From 244ef243d75145a01d9029589de65be51299b3f3 Mon Sep 17 00:00:00 2001 From: Krzysztof Bielicki Date: Tue, 10 Mar 2015 10:44:06 +0100 Subject: [#514] Add support for ignoring payload params in multipart/form-data --- libmproxy/console/contentview.py | 24 ++---------------------- libmproxy/flow.py | 2 +- libmproxy/protocol/http.py | 21 ++++++++++++++++++++- libmproxy/utils.py | 27 +++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/libmproxy/console/contentview.py b/libmproxy/console/contentview.py index 582723bb..84e9946d 100644 --- a/libmproxy/console/contentview.py +++ b/libmproxy/console/contentview.py @@ -210,33 +210,13 @@ class ViewMultipart: prompt = ("multipart", "m") content_types = ["multipart/form-data"] def __call__(self, hdrs, content, limit): - v = hdrs.get_first("content-type") + v = utils.multipartdecode(hdrs, content) if v: - v = utils.parse_content_type(v) - if not v: - return - boundary = v[2].get("boundary") - if not boundary: - return - - rx = re.compile(r'\bname="([^"]+)"') - keys = [] - vals = [] - - for i in content.split("--" + boundary): - parts = i.splitlines() - if len(parts) > 1 and parts[0][0:2] != "--": - match = rx.search(parts[1]) - if match: - keys.append(match.group(1) + ":") - vals.append(netlib.utils.cleanBin( - "\n".join(parts[3+parts[2:].index(""):]) - )) r = [ urwid.Text(("highlight", "Form data:\n")), ] r.extend(common.format_keyvals( - zip(keys, vals), + v, key = "header", val = "text" )) diff --git a/libmproxy/flow.py b/libmproxy/flow.py index 43580109..0e9e481c 100644 --- a/libmproxy/flow.py +++ b/libmproxy/flow.py @@ -236,7 +236,7 @@ class ServerPlaybackState: ] if not self.ignore_content: - form_contents = r.get_form_urlencoded() + form_contents = r.get_form() if self.ignore_payload_params and form_contents: key.extend( p for p in form_contents diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 49310ec3..512cf75b 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -15,6 +15,7 @@ from ..proxy.connection import ServerConnection from .. import encoding, utils, controller, stateobject, proxy HDR_FORM_URLENCODED = "application/x-www-form-urlencoded" +HDR_FORM_MULTIPART = "multipart/form-data" CONTENT_MISSING = 0 @@ -507,6 +508,19 @@ class HTTPRequest(HTTPMessage): """ self.headers["Host"] = [self.host] + def get_form(self): + """ + Retrieves the URL-encoded or multipart form data, returning an ODict object. + Returns an empty ODict if there is no data or the content-type + indicates non-form data. + """ + if self.content: + if self.headers.in_any("content-type", HDR_FORM_URLENCODED, True): + return self.get_form_urlencoded() + elif self.headers.in_any("content-type", HDR_FORM_MULTIPART, True): + return self.get_form_multipart() + return ODict([]) + def get_form_urlencoded(self): """ Retrieves the URL-encoded form data, returning an ODict object. @@ -514,7 +528,12 @@ class HTTPRequest(HTTPMessage): indicates non-form data. """ if self.content and self.headers.in_any("content-type", HDR_FORM_URLENCODED, True): - return ODict(utils.urldecode(self.content)) + return ODict(utils.urldecode(self.content)) + return ODict([]) + + def get_form_multipart(self): + if self.content and self.headers.in_any("content-type", HDR_FORM_MULTIPART, True): + return ODict(utils.multipartdecode(self.headers, self.content)) return ODict([]) def set_form_urlencoded(self, odict): diff --git a/libmproxy/utils.py b/libmproxy/utils.py index 51f2dc26..b84c589a 100644 --- a/libmproxy/utils.py +++ b/libmproxy/utils.py @@ -69,6 +69,33 @@ def urlencode(s): return urllib.urlencode(s, False) +def multipartdecode(hdrs, content): + """ + Takes a multipart boundary encoded string and returns list of (key, value) tuples. + """ + v = hdrs.get_first("content-type") + if v: + v = parse_content_type(v) + if not v: + return [] + boundary = v[2].get("boundary") + if not boundary: + return [] + + rx = re.compile(r'\bname="([^"]+)"') + r = [] + + for i in content.split("--" + boundary): + parts = i.splitlines() + if len(parts) > 1 and parts[0][0:2] != "--": + match = rx.search(parts[1]) + if match: + key = match.group(1) + value = "".join(parts[3+parts[2:].index(""):]) + r.append((key, value)) + return r + return [] + def pretty_size(size): suffixes = [ ("B", 2**10), -- cgit v1.2.3 From 953f9aa64166451a07502f05c15db47c053e6081 Mon Sep 17 00:00:00 2001 From: Krzysztof Bielicki Date: Mon, 16 Mar 2015 10:23:50 +0100 Subject: Added tests --- test/test_protocol_http.py | 21 +++++++++++++++++++++ test/test_utils.py | 19 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/test_protocol_http.py b/test/test_protocol_http.py index 16870777..23c3f469 100644 --- a/test/test_protocol_http.py +++ b/test/test_protocol_http.py @@ -1,3 +1,4 @@ +from mock import MagicMock from libmproxy.protocol.http import * from cStringIO import StringIO import tutils, tservers @@ -112,6 +113,26 @@ class TestHTTPRequest: r = tutils.treq() assert repr(r) + def test_get_form_for_urlencoded(self): + r = tutils.treq() + r.headers.add("content-type", "application/x-www-form-urlencoded") + r.get_form_urlencoded = MagicMock() + + r.get_form() + + assert r.get_form_urlencoded.called + + def test_get_form_for_multipart(self): + r = tutils.treq() + r.headers.add("content-type", "multipart/form-data") + r.get_form_multipart = MagicMock() + + r.get_form() + + assert r.get_form_multipart.called + + + class TestHTTPResponse: def test_read_from_stringio(self): diff --git a/test/test_utils.py b/test/test_utils.py index 78d1c072..a7902910 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,5 +1,5 @@ import json -from libmproxy import utils +from libmproxy import utils, flow import tutils utils.CERT_SLEEP_TIME = 0 @@ -52,6 +52,23 @@ def test_urldecode(): s = "one=two&three=four" assert len(utils.urldecode(s)) == 2 +def test_multipartdecode(): + boundary = 'somefancyboundary' + headers = flow.ODict([('content-type', ('multipart/form-data; boundary=%s' % boundary))]) + content = "--{0}\n" \ + "Content-Disposition: form-data; name=\"field1\"\n\n" \ + "value1\n" \ + "--{0}\n" \ + "Content-Disposition: form-data; name=\"field2\"\n\n" \ + "value2\n" \ + "--{0}--".format(boundary) + + form = utils.multipartdecode(headers, content) + + assert len(form) == 2 + assert form[0] == ('field1', 'value1') + assert form[1] == ('field2', 'value2') + def test_pretty_duration(): assert utils.pretty_duration(0.00001) == "0ms" assert utils.pretty_duration(0.0001) == "0ms" -- cgit v1.2.3 From 968d94d4710616ebf94cde4f3c35d469e227e910 Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Tue, 17 Mar 2015 00:26:42 -0400 Subject: Removes all of the platform specific certificate installation instructions. --- doc-src/_nav.html | 7 --- .../android-settingssecurityinstallca.png | Bin 57723 -> 0 bytes .../certinstall/android-settingssecuritymenu.png | Bin 75679 -> 0 bytes .../android-settingssecurityuserinstalledca.png | Bin 47263 -> 0 bytes .../certinstall/android-shellwgetmitmproxyca.png | Bin 22198 -> 0 bytes doc-src/certinstall/android.html | 53 --------------------- doc-src/certinstall/firefox.html | 31 ------------ doc-src/certinstall/index.py | 7 --- doc-src/certinstall/ios-simulator.html | 23 --------- doc-src/certinstall/ios.html | 27 ----------- doc-src/certinstall/java.html | 13 ----- doc-src/certinstall/osx.html | 16 ------- doc-src/certinstall/windows7.html | 35 -------------- doc-src/ssl.html | 10 +--- doc-src/tutorials/gamecenter.html | 5 +- 15 files changed, 3 insertions(+), 224 deletions(-) delete mode 100644 doc-src/certinstall/android-settingssecurityinstallca.png delete mode 100644 doc-src/certinstall/android-settingssecuritymenu.png delete mode 100644 doc-src/certinstall/android-settingssecurityuserinstalledca.png delete mode 100644 doc-src/certinstall/android-shellwgetmitmproxyca.png delete mode 100644 doc-src/certinstall/android.html delete mode 100644 doc-src/certinstall/firefox.html delete mode 100644 doc-src/certinstall/ios-simulator.html delete mode 100644 doc-src/certinstall/ios.html delete mode 100644 doc-src/certinstall/java.html delete mode 100644 doc-src/certinstall/osx.html delete mode 100644 doc-src/certinstall/windows7.html diff --git a/doc-src/_nav.html b/doc-src/_nav.html index 69175c0c..91d2118f 100644 --- a/doc-src/_nav.html +++ b/doc-src/_nav.html @@ -31,13 +31,6 @@ $!nav("ssl.html", this, state)!$ $!nav("certinstall/webapp.html", this, state)!$ - $!nav("certinstall/android.html", this, state)!$ - $!nav("certinstall/firefox.html", this, state)!$ - $!nav("certinstall/ios.html", this, state)!$ - $!nav("certinstall/ios-simulator.html", this, state)!$ - $!nav("certinstall/java.html", this, state)!$ - $!nav("certinstall/osx.html", this, state)!$ - $!nav("certinstall/windows7.html", this, state)!$ $!nav("transparent.html", this, state)!$ diff --git a/doc-src/certinstall/android-settingssecurityinstallca.png b/doc-src/certinstall/android-settingssecurityinstallca.png deleted file mode 100644 index f0f97273..00000000 Binary files a/doc-src/certinstall/android-settingssecurityinstallca.png and /dev/null differ diff --git a/doc-src/certinstall/android-settingssecuritymenu.png b/doc-src/certinstall/android-settingssecuritymenu.png deleted file mode 100644 index fea412fe..00000000 Binary files a/doc-src/certinstall/android-settingssecuritymenu.png and /dev/null differ diff --git a/doc-src/certinstall/android-settingssecurityuserinstalledca.png b/doc-src/certinstall/android-settingssecurityuserinstalledca.png deleted file mode 100644 index 1f7717ad..00000000 Binary files a/doc-src/certinstall/android-settingssecurityuserinstalledca.png and /dev/null differ diff --git a/doc-src/certinstall/android-shellwgetmitmproxyca.png b/doc-src/certinstall/android-shellwgetmitmproxyca.png deleted file mode 100644 index 4a4e326f..00000000 Binary files a/doc-src/certinstall/android-shellwgetmitmproxyca.png and /dev/null differ diff --git a/doc-src/certinstall/android.html b/doc-src/certinstall/android.html deleted file mode 100644 index 73fc4d8b..00000000 --- a/doc-src/certinstall/android.html +++ /dev/null @@ -1,53 +0,0 @@ -The proxy situation on Android is [an -embarrasment](http://code.google.com/p/android/issues/detail?id=1273). It's -scarcely credible, but Android didn't have a global proxy setting at all until -quite recently, and it's still not supported on many common Android versions. -In the meantime the app ecosystem has grown used to life without this basic -necessity, and many apps merrily ignore it even if it's there. This situation -is improving, but in many circumstances using [transparent -mode](@!urlTo("transparent.html")!@) is mandatory for testing Android apps. - -We used both an Asus Transformer Prime TF201 (Android 4.0.3) and a Nexus 4 -(Android 4.4.4) in the examples below - your device may differ, but the broad -process should be similar. On **emulated devices**, there are some [additional -quirks](https://github.com/mitmproxy/mitmproxy/issues/204#issuecomment-32837093) -to consider. - - -## Getting the certificate onto the device - -The easiest way to get the certificate to the device is to use [the web -app](@!urlTo("webapp.html")!@). In the rare cases where the web app doesn't -work, you will need to get the __mitmproxy-ca-cert.cer__ file into the -__/sdcard__ folder on the device (/sdcard/Download on older devices). This can -be accomplished in a number of ways: - -- If you have the Android Developer Tools installed, you can use [__adb -push__](http://developer.android.com/tools/help/adb.html). -- Using a file transfer program like wget (installed on the Android device) to -copy the file over. -- Transfer the file using external media like an SD Card. - -Once we have the certificate on the local disk, we need to import it into the -list of trusted CAs. Go to Settings -> Security -> Credential Storage, -and select "Install from storage": - - - -The certificate in /sdcard is automatically located and offered for -installation. Installing the cert will delete the download file from the local -disk. - - -## Installing the certificate - -You should now see something like this (you may have to explicitly name the -certificate): - - - -Click OK, and you should then see the certificate listed in the Trusted -Credentials store: - - - diff --git a/doc-src/certinstall/firefox.html b/doc-src/certinstall/firefox.html deleted file mode 100644 index bb9ba05b..00000000 --- a/doc-src/certinstall/firefox.html +++ /dev/null @@ -1,31 +0,0 @@ -## Get the certificate to the browser - -The easiest way to get the certificate to the browser is to use [the web -app](@!urlTo("webapp.html")!@). If this fails, do the following: - - -
    -
  1. If needed, copy the ~/.mitmproxy/mitmproxy-ca-cert.pem file to the target.
  2. - -
  3. Open preferences, click on "Advanced", then select"Certificates": - -
  4. - -
  5. Click "View Certificates", "Import", and select the certificate file: - -
  6. - -
- - -## Installing the certificate - -
    -
  1. Tick "Trust this CA to identify web sites", and click "Ok": - -
  2. - -
  3. You should now see the mitmproxy certificate listed in the Authorities - tab.
  4. -
- diff --git a/doc-src/certinstall/index.py b/doc-src/certinstall/index.py index d6b1e417..fd422cb3 100644 --- a/doc-src/certinstall/index.py +++ b/doc-src/certinstall/index.py @@ -2,12 +2,5 @@ from countershape import Page pages = [ Page("webapp.html", "Using the Web App"), - Page("firefox.html", "Firefox"), - Page("osx.html", "OSX"), - Page("windows7.html", "Windows 7"), - Page("ios.html", "IOS"), - Page("ios-simulator.html", "IOS Simulator"), - Page("android.html", "Android"), - Page("java.html", "Java"), Page("mitm.it-error.html", "Error: No proxy configured"), ] diff --git a/doc-src/certinstall/ios-simulator.html b/doc-src/certinstall/ios-simulator.html deleted file mode 100644 index 9eb98108..00000000 --- a/doc-src/certinstall/ios-simulator.html +++ /dev/null @@ -1,23 +0,0 @@ - -How to install the __mitmproxy__ certificate authority in the IOS simulator: - -
    - -
  1. First, check out the ADVTrustStore tool - from github.
  2. - -
  3. Now, run the following command: - -
    ./iosCertTrustManager.py -a ~/.mitmproxy/mitmproxy-ca-cert.pem
    - -
  4. - -
- - -Note that although the IOS simulator has its own certificate store, it shares -the proxy settings of the host operating system. You will therefore to have -configure your OSX host's proxy settings to use the mitmproxy instance you want -to test with. - diff --git a/doc-src/certinstall/ios.html b/doc-src/certinstall/ios.html deleted file mode 100644 index c12d65f6..00000000 --- a/doc-src/certinstall/ios.html +++ /dev/null @@ -1,27 +0,0 @@ - -## Getting the certificate onto the device - -The easiest way to get the certificate to the device is to use [the web -app](@!urlTo("webapp.html")!@). In the rare cases where the web app doesn't -work, you will need to get the __mitmproxy-ca-cert.pem__ file to the device to -install it. The easiest way to accomplish this is to set up the Mail app on the -device, and to email it over as an attachment. Open the email, tap on the -attachment, then proceed with the install. - - -## Installing the certificate - -
    -
  1. You will be prompted to install a profile. Click "Install": - -
  2. - -
  3. Accept the warning by clicking "Install" again: - -
  4. - -
  5. The certificate should now be trusted: - -
  6. - -
diff --git a/doc-src/certinstall/java.html b/doc-src/certinstall/java.html deleted file mode 100644 index f6420991..00000000 --- a/doc-src/certinstall/java.html +++ /dev/null @@ -1,13 +0,0 @@ - -You can add the mitmproxy certificates to the Java trust store using -[keytool](http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/keytool.html). -On OSX, the required command looks like this: - -
-sudo keytool -importcert -alias mitmproxy -storepass "password" \
--keystore /System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security/cacerts \
--trustcacerts -file ~/.mitmproxy/mitmproxy-ca-cert.pem
-
- -Note that your store password will (hopefully) be different from the one above. - diff --git a/doc-src/certinstall/osx.html b/doc-src/certinstall/osx.html deleted file mode 100644 index a532d538..00000000 --- a/doc-src/certinstall/osx.html +++ /dev/null @@ -1,16 +0,0 @@ - -How to install the __mitmproxy__ certificate authority in OSX: - -
    - -
  1. Open Finder, and double-click on the mitmproxy-ca-cert.pem file.
  2. - -
  3. You will be prompted to add the certificate. Click "Always Trust": - - -
  4. - -
  5. You may be prompted for your password. You should now see the - mitmproxy cert listed under "Certificates".
  6. -
- diff --git a/doc-src/certinstall/windows7.html b/doc-src/certinstall/windows7.html deleted file mode 100644 index 7a4cc3d2..00000000 --- a/doc-src/certinstall/windows7.html +++ /dev/null @@ -1,35 +0,0 @@ - -How to install the __mitmproxy__ certificate authority in Windows 7: - -
    - -
  1. The easiest way to get the certificate to the device is to use the web app. If this fails for some - reason, simply copy the ~/.mitmproxy/mitmproxy-ca-cert.p12 file to the - target system and double-click it.
  2. - -
  3. - You should see a certificate import wizard: - - -
  4. - -
  5. - Click "Next" until you're prompted for the certificate store: - - - -
  6. - - -
  7. -

    Select "Place all certificates in the following store", and select "Trusted Root Certification Authorities":

    - - - -
  8. - -
  9. Click "Next" and "Finish".
  10. - -
- diff --git a/doc-src/ssl.html b/doc-src/ssl.html index de45bd29..cccde1b7 100644 --- a/doc-src/ssl.html +++ b/doc-src/ssl.html @@ -87,13 +87,5 @@ You can use your own certificate authority by passing the --confdir o mitmproxy will then look for mitmproxy-ca.pem in the specified directory. If no such file exists, it will be generated automatically. -Installing the mitmproxy CA ---------------------------- - -* [Firefox](@!urlTo("certinstall/firefox.html")!@) -* [OSX](@!urlTo("certinstall/osx.html")!@) -* [Windows 7](@!urlTo("certinstall/windows7.html")!@) -* [iPhone/iPad](@!urlTo("certinstall/ios.html")!@) -* [IOS Simulator](@!urlTo("certinstall/ios-simulator.html")!@) -* [Android](@!urlTo("certinstall/android.html")!@) + diff --git a/doc-src/tutorials/gamecenter.html b/doc-src/tutorials/gamecenter.html index 5998f889..b51b6faf 100644 --- a/doc-src/tutorials/gamecenter.html +++ b/doc-src/tutorials/gamecenter.html @@ -2,9 +2,8 @@ ## The setup In this tutorial, I'm going to show you how simple it is to creatively -interfere with Apple Game Center traffic using mitmproxy. To set things up, I -registered my mitmproxy CA certificate with my iPhone - there's a [step by step -set of instructions](@!urlTo("certinstall/ios.html")!@) elsewhere in this manual. I then +interfere with Apple Game Center traffic using mitmproxy. To set things up, +you must install the [mitmproxy root certificate](@!urlTo("certinstall/webapp.html")!@) elsewhere in this manual. I then started mitmproxy on my desktop, and configured the iPhone to use it as a proxy. -- cgit v1.2.3 From f3dab52a6297d4ea2fe0f0bc444bf0a3265e887a Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Tue, 17 Mar 2015 00:30:18 -0400 Subject: Better english --- doc-src/tutorials/gamecenter.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc-src/tutorials/gamecenter.html b/doc-src/tutorials/gamecenter.html index b51b6faf..d192232c 100644 --- a/doc-src/tutorials/gamecenter.html +++ b/doc-src/tutorials/gamecenter.html @@ -3,7 +3,7 @@ In this tutorial, I'm going to show you how simple it is to creatively interfere with Apple Game Center traffic using mitmproxy. To set things up, -you must install the [mitmproxy root certificate](@!urlTo("certinstall/webapp.html")!@) elsewhere in this manual. I then +you must install the [mitmproxy root certificate](@!urlTo("certinstall/webapp.html")!@). I then started mitmproxy on my desktop, and configured the iPhone to use it as a proxy. -- cgit v1.2.3 From e0e36f5dae4572ea4053821e6fef1487de87642e Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Wed, 18 Mar 2015 00:22:18 -0400 Subject: consolidated down all SSL documentation into one document --- doc-src/_nav.html | 3 +- doc-src/certinstall/index.py | 2 +- doc-src/certinstall/webapp.html | 13 ------ doc-src/index.py | 1 - doc-src/ssl.html | 91 --------------------------------------- doc-src/tutorials/gamecenter.html | 2 +- 6 files changed, 3 insertions(+), 109 deletions(-) delete mode 100644 doc-src/certinstall/webapp.html delete mode 100644 doc-src/ssl.html diff --git a/doc-src/_nav.html b/doc-src/_nav.html index 91d2118f..3efff40b 100644 --- a/doc-src/_nav.html +++ b/doc-src/_nav.html @@ -29,8 +29,7 @@ - $!nav("ssl.html", this, state)!$ - $!nav("certinstall/webapp.html", this, state)!$ + $!nav("certinstall/ssl.html", this, state)!$ $!nav("transparent.html", this, state)!$ diff --git a/doc-src/certinstall/index.py b/doc-src/certinstall/index.py index fd422cb3..67e6185b 100644 --- a/doc-src/certinstall/index.py +++ b/doc-src/certinstall/index.py @@ -1,6 +1,6 @@ from countershape import Page pages = [ - Page("webapp.html", "Using the Web App"), + Page("ssl.html", "SSL Options"), Page("mitm.it-error.html", "Error: No proxy configured"), ] diff --git a/doc-src/certinstall/webapp.html b/doc-src/certinstall/webapp.html deleted file mode 100644 index 478da96c..00000000 --- a/doc-src/certinstall/webapp.html +++ /dev/null @@ -1,13 +0,0 @@ - -By far the easiest way to install the mitmproxy certs is to use the built-in -web app. To do this, start mitmproxy and configure your target device with the -correct proxy settings. Now start a browser on the device, and visit the magic -domain **mitm.it**. You should see something like this: - - - -Just click on the relevant icon, and then follow the setup instructions -for the platform you're on. - -Make sure you aren't using a bandwith optimizer (like Google's Data Compression -Proxy on Chrome for Android) or the page will not load. diff --git a/doc-src/index.py b/doc-src/index.py index 753f90a5..1c1203f8 100644 --- a/doc-src/index.py +++ b/doc-src/index.py @@ -67,7 +67,6 @@ pages = [ Page("mitmdump.html", "mitmdump"), Page("config.html", "configuration"), - Page("ssl.html", "Overview"), Directory("certinstall"), Directory("scripting"), Directory("tutorials"), diff --git a/doc-src/ssl.html b/doc-src/ssl.html deleted file mode 100644 index cccde1b7..00000000 --- a/doc-src/ssl.html +++ /dev/null @@ -1,91 +0,0 @@ - -The first time __mitmproxy__ or __mitmdump__ is run, a set of certificate files -for the mitmproxy Certificate Authority are created in the config directory -(~/.mitmproxy by default). This CA is used for on-the-fly generation of dummy -certificates for SSL interception. Since your browser won't trust the -__mitmproxy__ CA out of the box (and rightly so), you will see an SSL cert -warning every time you visit a new SSL domain through __mitmproxy__. When -you're testing a single site through a browser, just accepting the bogus SSL -cert manually is not too much trouble, but there are a many circumstances where -you will want to configure your testing system or browser to trust the -__mitmproxy__ CA as a signing root authority. - - -CA and cert files ------------------ - -The files created by mitmproxy in the .mitmproxy directory are as follows: - - - - - - - - - - - - - - - - - - -
mitmproxy-ca.pemThe private key and certificate in PEM format.
mitmproxy-ca-cert.pemThe certificate in PEM format. Use this to distribute to most - non-Windows platforms.
mitmproxy-ca-cert.p12The certificate in PKCS12 format. For use on Windows.
mitmproxy-ca-cert.cerSame file as .pem, but with an extension expected by some Android - devices.
- - -Using a custom certificate --------------------------- - -You can use your own certificate by passing the --cert option to mitmproxy. mitmproxy then uses the provided -certificate for interception of the specified domains instead of generating a cert signed by its own CA. - -The certificate file is expected to be in the PEM format. -You can include intermediary certificates right below your leaf certificate, so that you PEM file roughly looks like -this: - -
------BEGIN PRIVATE KEY-----
-<private key>
------END PRIVATE KEY-----
------BEGIN CERTIFICATE-----
-<cert>
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-<intermediary cert (optional)>
------END CERTIFICATE-----
-
- -For example, you can generate a certificate in this format using these instructions: - -
-> openssl genrsa -out cert.key 8192
-> openssl req -new -x509 -key cert.key -out cert.crt
-    (Specify the mitm domain as Common Name, e.g. *.google.com)
-> cat cert.key cert.crt > cert.pem
-> mitmproxy --cert=cert.pem
-
- -Using a client side certificate ------------------------------------- -You can use a client certificate by passing the --client-certs DIRECTORY option to mitmproxy. -If you visit example.org, mitmproxy looks for a file named example.org.pem in the specified directory -and uses this as the client cert. The certificate file needs to be in the PEM format and should contain -both the unencrypted private key as well as the certificate. - - -Using a custom certificate authority ------------------------------------- - -By default, mitmproxy will (generate and) use ~/.mitmproxy/mitmproxy-ca.pem as the default certificate -authority to generate certificates for all domains for which no custom certificate is provided (see above). -You can use your own certificate authority by passing the --confdir option to mitmproxy. -mitmproxy will then look for mitmproxy-ca.pem in the specified directory. If no such file exists, -it will be generated automatically. - - - diff --git a/doc-src/tutorials/gamecenter.html b/doc-src/tutorials/gamecenter.html index d192232c..8d2e9bc5 100644 --- a/doc-src/tutorials/gamecenter.html +++ b/doc-src/tutorials/gamecenter.html @@ -3,7 +3,7 @@ In this tutorial, I'm going to show you how simple it is to creatively interfere with Apple Game Center traffic using mitmproxy. To set things up, -you must install the [mitmproxy root certificate](@!urlTo("certinstall/webapp.html")!@). I then +you must install the [mitmproxy root certificate](@!urlTo("certinstall/ssl.html")!@). I then started mitmproxy on my desktop, and configured the iPhone to use it as a proxy. -- cgit v1.2.3 From 36bec7b77e1a8c02211c706b3e651fee13a3b3e2 Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Wed, 18 Mar 2015 00:29:54 -0400 Subject: now actually tracking ssl.html --- doc-src/certinstall/ssl.html | 113 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 doc-src/certinstall/ssl.html diff --git a/doc-src/certinstall/ssl.html b/doc-src/certinstall/ssl.html new file mode 100644 index 00000000..8b2b8ed7 --- /dev/null +++ b/doc-src/certinstall/ssl.html @@ -0,0 +1,113 @@ +SSL traffic poses a potential problem when using mitmproxy, as it is encrypted, it is opaque to inspection. +In order to be able to decrypt the traffic, you must use a certificate that the client, whose traffic you are intercepting, trusts. +This document outlines the different options you have for either using the certificate that mitmproxy generates or using your own. + +Quick Setup +----------- + +By far the easiest way to install the mitmproxy certificates is to use the built-in +web app. To do this, start mitmproxy and configure your target device with the +correct proxy settings. Now start a browser on the device, and visit the domain **mitm.it**. +You should see something like this: + + + +Just click on the relevant icon, and then follow the setup instructions +for the platform you're on. + +Certificates are installed via several different methods depending on the client. +There are too many to go into in this document, consult the documentation for +the client that you would to have trust the mitmproxy root certificate, +for specific installation instructions. + + +More On mitmproxy Certificates +------------------------------ + +The first time __mitmproxy__ or __mitmdump__ is run, the mitmproxy Certificate +Authority(CA) is created in the config directory (~/.mitmproxy by default). +This CA is used for on-the-fly generation of dummy certificates for each of the +SSL sites that your client visits. Since your browser won't trust the +__mitmproxy__ CA out of the box , you will see an SSL certificate +warning every time you visit a new SSL domain through __mitmproxy__. When +you are testing a single site through a browser, just accepting the bogus SSL +cert manually is not too much trouble, but there are a many circumstances where +you will want to configure your testing system or browser to trust the +__mitmproxy__ CA as a signing root authority. + + +CA and cert files +----------------- + +The files created by mitmproxy in the .mitmproxy directory are as follows: + + + + + + + + + + + + + + + + + + +
mitmproxy-ca.pemThe private key and certificate in PEM format.
mitmproxy-ca-cert.pemThe certificate in PEM format. Use this to distribute to most + non-Windows platforms.
mitmproxy-ca-cert.p12The certificate in PKCS12 format. For use on Windows.
mitmproxy-ca-cert.cerSame file as .pem, but with an extension expected by some Android + devices.
+ + +Using a custom certificate +-------------------------- + +You can use your own certificate by passing the --cert option to mitmproxy. mitmproxy then uses the provided +certificate for interception of the specified domains instead of generating a certificate signed by its own CA. + +The certificate file is expected to be in the PEM format. +You can include intermediary certificates right below your leaf certificate, so that you PEM file roughly looks like +this: + +
+-----BEGIN PRIVATE KEY-----
+<private key>
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+<cert>
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+<intermediary cert (optional)>
+-----END CERTIFICATE-----
+
+ +For example, you can generate a certificate in this format using these instructions: + +
+> openssl genrsa -out cert.key 2048
+> openssl req -new -x509 -key cert.key -out cert.crt
+    (Specify the mitm domain as Common Name, e.g. *.google.com)
+> cat cert.key cert.crt > cert.pem
+> mitmproxy --cert=cert.pem
+
+ +Using a client side certificate +------------------------------------ +You can use a client certificate by passing the --client-certs DIRECTORY option to mitmproxy. +If you visit example.org, mitmproxy looks for a file named example.org.pem in the specified directory +and uses this as the client cert. The certificate file needs to be in the PEM format and should contain +both the unencrypted private key as well as the certificate. + + +Using a custom certificate authority +------------------------------------ + +By default, mitmproxy will (generate and) use ~/.mitmproxy/mitmproxy-ca.pem as the default certificate +authority to generate certificates for all domains for which no custom certificate is provided (see above). +You can use your own certificate authority by passing the --confdir option to mitmproxy. +mitmproxy will then look for mitmproxy-ca.pem in the specified directory. If no such file exists, +it will be generated automatically. -- cgit v1.2.3 From d7e53e6573426c40ac7cfbaa7754380985227eb1 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 09:30:29 +1300 Subject: Fix crashes on mouse click when input is being handled --- libmproxy/console/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 198b7bbe..70b82d1d 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -617,8 +617,6 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_execute(k) elif k == "enter": self.prompt_execute() - else: - self.view.keypress(self.loop.screen_size, k) else: k = self.view.keypress(self.loop.screen_size, k) if k: @@ -943,7 +941,7 @@ class ConsoleMaster(flow.FlowMaster): mkup.append(",") prompt.extend(mkup) prompt.append(")? ") - self.onekey = "".join(i[1] for i in keys) + self.onekey = set(i[1] for i in keys) self.prompt(prompt, "", callback, *args) def prompt_done(self): -- cgit v1.2.3 From a3f4296bf1ba0ac1a72d5a44a504d375707fdc39 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 10:02:34 +1300 Subject: Explicitly handle keyboard interrupt in mitmproxy Fixes #522 --- libmproxy/console/__init__.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 70b82d1d..9796677f 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -7,6 +7,7 @@ import tempfile import os import os.path import shlex +import signal import stat import subprocess import sys @@ -25,7 +26,8 @@ EVENTLOG_SIZE = 500 class _PathCompleter: def __init__(self, _testing=False): """ - _testing: disables reloading of the lookup table to make testing possible. + _testing: disables reloading of the lookup table to make testing + possible. """ self.lookup, self.offset = None, None self.final = None @@ -37,7 +39,8 @@ class _PathCompleter: def complete(self, txt): """ - Returns the next completion for txt, or None if there is no completion. + Returns the next completion for txt, or None if there is no + completion. """ path = os.path.expanduser(txt) if not self.lookup: @@ -702,14 +705,6 @@ class ConsoleMaster(flow.FlowMaster): self.edit_scripts ) ) - #if self.scripts: - # self.load_script(None) - #else: - # self.path_prompt( - # "Set script: ", - # self.state.last_script, - # self.set_script - # ) elif k == "S": if not self.server_playback: self.path_prompt( @@ -799,6 +794,14 @@ class ConsoleMaster(flow.FlowMaster): sys.exit(1) self.loop.set_alarm_in(0.01, self.ticker) + + # It's not clear why we need to handle this explicitly - without this, + # mitmproxy hangs on keyboard interrupt. Remove if we ever figure it + # out. + def exit(s, f): + raise urwid.ExitMainLoop + signal.signal(signal.SIGINT, exit) + try: self.loop.run() except Exception: -- cgit v1.2.3 From 560e44c637e4f1fcbeba1305fc1eb39e3d796013 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 10:54:57 +1300 Subject: Pull PathEdit out into its own file. --- libmproxy/console/__init__.py | 69 ++----------------------------------------- libmproxy/console/pathedit.py | 69 +++++++++++++++++++++++++++++++++++++++++++ test/test_console.py | 43 --------------------------- test/test_console_pathedit.py | 48 ++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 110 deletions(-) create mode 100644 libmproxy/console/pathedit.py create mode 100644 test/test_console_pathedit.py diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 9796677f..013c8003 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -18,76 +18,11 @@ import weakref from .. import controller, utils, flow, script from . import flowlist, flowview, help, common -from . import grideditor, palettes, contentview, flowdetailview +from . import grideditor, palettes, contentview, flowdetailview, pathedit EVENTLOG_SIZE = 500 -class _PathCompleter: - def __init__(self, _testing=False): - """ - _testing: disables reloading of the lookup table to make testing - possible. - """ - self.lookup, self.offset = None, None - self.final = None - self._testing = _testing - - def reset(self): - self.lookup = None - self.offset = -1 - - def complete(self, txt): - """ - Returns the next completion for txt, or None if there is no - completion. - """ - path = os.path.expanduser(txt) - if not self.lookup: - if not self._testing: - # Lookup is a set of (display value, actual value) tuples. - self.lookup = [] - if os.path.isdir(path): - files = glob.glob(os.path.join(path, "*")) - prefix = txt - else: - files = glob.glob(path+"*") - prefix = os.path.dirname(txt) - prefix = prefix or "./" - for f in files: - display = os.path.join(prefix, os.path.basename(f)) - if os.path.isdir(f): - display += "/" - self.lookup.append((display, f)) - if not self.lookup: - self.final = path - return path - self.lookup.sort() - self.offset = -1 - self.lookup.append((txt, txt)) - self.offset += 1 - if self.offset >= len(self.lookup): - self.offset = 0 - ret = self.lookup[self.offset] - self.final = ret[1] - return ret[0] - - -class PathEdit(urwid.Edit, _PathCompleter): - def __init__(self, *args, **kwargs): - urwid.Edit.__init__(self, *args, **kwargs) - _PathCompleter.__init__(self) - - def keypress(self, size, key): - if key == "tab": - comp = self.complete(self.get_edit_text()) - self.set_edit_text(comp) - self.set_edit_pos(len(comp)) - else: - self.reset() - return urwid.Edit.keypress(self, size, key) - - class ActionBar(urwid.WidgetWrap): def __init__(self): self.message("") @@ -97,7 +32,7 @@ class ActionBar(urwid.WidgetWrap): def path_prompt(self, prompt, text): self.expire = None - self._w = PathEdit(prompt, text) + self._w = pathedit.PathEdit(prompt, text) def prompt(self, prompt, text = ""): self.expire = None diff --git a/libmproxy/console/pathedit.py b/libmproxy/console/pathedit.py new file mode 100644 index 00000000..53cda3be --- /dev/null +++ b/libmproxy/console/pathedit.py @@ -0,0 +1,69 @@ +import glob +import os.path + +import urwid + + +class _PathCompleter: + def __init__(self, _testing=False): + """ + _testing: disables reloading of the lookup table to make testing + possible. + """ + self.lookup, self.offset = None, None + self.final = None + self._testing = _testing + + def reset(self): + self.lookup = None + self.offset = -1 + + def complete(self, txt): + """ + Returns the next completion for txt, or None if there is no + completion. + """ + path = os.path.expanduser(txt) + if not self.lookup: + if not self._testing: + # Lookup is a set of (display value, actual value) tuples. + self.lookup = [] + if os.path.isdir(path): + files = glob.glob(os.path.join(path, "*")) + prefix = txt + else: + files = glob.glob(path+"*") + prefix = os.path.dirname(txt) + prefix = prefix or "./" + for f in files: + display = os.path.join(prefix, os.path.basename(f)) + if os.path.isdir(f): + display += "/" + self.lookup.append((display, f)) + if not self.lookup: + self.final = path + return path + self.lookup.sort() + self.offset = -1 + self.lookup.append((txt, txt)) + self.offset += 1 + if self.offset >= len(self.lookup): + self.offset = 0 + ret = self.lookup[self.offset] + self.final = ret[1] + return ret[0] + + +class PathEdit(urwid.Edit, _PathCompleter): + def __init__(self, *args, **kwargs): + urwid.Edit.__init__(self, *args, **kwargs) + _PathCompleter.__init__(self) + + def keypress(self, size, key): + if key == "tab": + comp = self.complete(self.get_edit_text()) + self.set_edit_text(comp) + self.set_edit_pos(len(comp)) + else: + self.reset() + return urwid.Edit.keypress(self, size, key) diff --git a/test/test_console.py b/test/test_console.py index d66bd8b0..419b94a7 100644 --- a/test/test_console.py +++ b/test/test_console.py @@ -104,48 +104,5 @@ def test_format_keyvals(): ) -class TestPathCompleter: - def test_lookup_construction(self): - c = console._PathCompleter() - - cd = tutils.test_data.path("completion") - ca = os.path.join(cd, "a") - assert c.complete(ca).endswith(normpath("/completion/aaa")) - assert c.complete(ca).endswith(normpath("/completion/aab")) - c.reset() - ca = os.path.join(cd, "aaa") - assert c.complete(ca).endswith(normpath("/completion/aaa")) - assert c.complete(ca).endswith(normpath("/completion/aaa")) - c.reset() - assert c.complete(cd).endswith(normpath("/completion/aaa")) - - def test_completion(self): - c = console._PathCompleter(True) - c.reset() - c.lookup = [ - ("a", "x/a"), - ("aa", "x/aa"), - ] - assert c.complete("a") == "a" - assert c.final == "x/a" - assert c.complete("a") == "aa" - assert c.complete("a") == "a" - - c = console._PathCompleter(True) - r = c.complete("l") - assert c.final.endswith(r) - - c.reset() - assert c.complete("/nonexistent") == "/nonexistent" - assert c.final == "/nonexistent" - c.reset() - assert c.complete("~") != "~" - - c.reset() - s = "thisisatotallynonexistantpathforsure" - assert c.complete(s) == s - assert c.final == s - - def test_options(): assert console.Options(kill=True) diff --git a/test/test_console_pathedit.py b/test/test_console_pathedit.py new file mode 100644 index 00000000..605e1e2f --- /dev/null +++ b/test/test_console_pathedit.py @@ -0,0 +1,48 @@ +import os +from os.path import normpath +from libmproxy.console import pathedit + +import tutils + + +class TestPathCompleter: + def test_lookup_construction(self): + c = pathedit._PathCompleter() + + cd = tutils.test_data.path("completion") + ca = os.path.join(cd, "a") + assert c.complete(ca).endswith(normpath("/completion/aaa")) + assert c.complete(ca).endswith(normpath("/completion/aab")) + c.reset() + ca = os.path.join(cd, "aaa") + assert c.complete(ca).endswith(normpath("/completion/aaa")) + assert c.complete(ca).endswith(normpath("/completion/aaa")) + c.reset() + assert c.complete(cd).endswith(normpath("/completion/aaa")) + + def test_completion(self): + c = pathedit._PathCompleter(True) + c.reset() + c.lookup = [ + ("a", "x/a"), + ("aa", "x/aa"), + ] + assert c.complete("a") == "a" + assert c.final == "x/a" + assert c.complete("a") == "aa" + assert c.complete("a") == "a" + + c = pathedit._PathCompleter(True) + r = c.complete("l") + assert c.final.endswith(r) + + c.reset() + assert c.complete("/nonexistent") == "/nonexistent" + assert c.final == "/nonexistent" + c.reset() + assert c.complete("~") != "~" + + c.reset() + s = "thisisatotallynonexistantpathforsure" + assert c.complete(s) == s + assert c.final == s -- cgit v1.2.3 From 558e0a41c25ed927a3bd3244e82e50f2c1ec9f1c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:00:24 +1300 Subject: Fix general prompt input. --- libmproxy/console/__init__.py | 3 +++ libmproxy/console/common.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 013c8003..5ff8e8d7 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -555,6 +555,9 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_execute(k) elif k == "enter": self.prompt_execute() + else: + if common.is_keypress(k): + self.view.keypress(self.loop.screen_size, k) else: k = self.view.keypress(self.loop.screen_size, k) if k: diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 3a708c7c..90204d79 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -31,6 +31,14 @@ METHOD_OPTIONS = [ ] +def is_keypress(k): + """ + Is this input event a keypress? + """ + if isinstance(k, basestring): + return True + + def highlight_key(s, k): l = [] parts = s.split(k, 1) -- cgit v1.2.3 From 241530eb0aa69c5a69bed979a1a2a3a23d473112 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:03:46 +1300 Subject: Remove cruft to work around an old Urwid bug --- libmproxy/console/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 5ff8e8d7..f3c8ee12 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -36,11 +36,6 @@ class ActionBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self.expire = None - # A (partial) workaround for this Urwid issue: - # https://github.com/Nic0/tyrs/issues/115 - # We can remove it once veryone is beyond 1.0.1 - if isinstance(prompt, basestring): - prompt = unicode(prompt) self._w = urwid.Edit(prompt, text or "") def message(self, message, expire=None): -- cgit v1.2.3 From 2f8ebfdce2165f1bd9196954a1d3bcdfec463494 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 11:08:04 +1300 Subject: Pull console StatusBar into its own file. --- libmproxy/console/__init__.py | 189 ++--------------------------------------- libmproxy/console/statusbar.py | 180 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 182 deletions(-) create mode 100644 libmproxy/console/statusbar.py diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f3c8ee12..5f564a20 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -1,6 +1,5 @@ from __future__ import absolute_import -import glob import mailcap import mimetypes import tempfile @@ -11,191 +10,17 @@ import signal import stat import subprocess import sys -import time import traceback import urwid import weakref -from .. import controller, utils, flow, script +from .. import controller, flow, script from . import flowlist, flowview, help, common -from . import grideditor, palettes, contentview, flowdetailview, pathedit +from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 -class ActionBar(urwid.WidgetWrap): - def __init__(self): - self.message("") - - def selectable(self): - return True - - def path_prompt(self, prompt, text): - self.expire = None - self._w = pathedit.PathEdit(prompt, text) - - def prompt(self, prompt, text = ""): - self.expire = None - self._w = urwid.Edit(prompt, text or "") - - def message(self, message, expire=None): - self.expire = expire - self._w = urwid.Text(message) - - -class StatusBar(urwid.WidgetWrap): - def __init__(self, master, helptext): - self.master, self.helptext = master, helptext - self.ab = ActionBar() - self.ib = urwid.WidgetWrap(urwid.Text("")) - self._w = urwid.Pile([self.ib, self.ab]) - - def get_status(self): - r = [] - - if self.master.setheaders.count(): - r.append("[") - r.append(("heading_key", "H")) - r.append("eaders]") - if self.master.replacehooks.count(): - r.append("[") - r.append(("heading_key", "R")) - r.append("eplacing]") - if self.master.client_playback: - r.append("[") - r.append(("heading_key", "cplayback")) - r.append(":%s to go]"%self.master.client_playback.count()) - if self.master.server_playback: - r.append("[") - r.append(("heading_key", "splayback")) - if self.master.nopop: - r.append(":%s in file]"%self.master.server_playback.count()) - else: - r.append(":%s to go]"%self.master.server_playback.count()) - if self.master.get_ignore_filter(): - r.append("[") - r.append(("heading_key", "I")) - r.append("gnore:%d]" % len(self.master.get_ignore_filter())) - if self.master.get_tcp_filter(): - r.append("[") - r.append(("heading_key", "T")) - r.append("CP:%d]" % len(self.master.get_tcp_filter())) - if self.master.state.intercept_txt: - r.append("[") - r.append(("heading_key", "i")) - r.append(":%s]"%self.master.state.intercept_txt) - if self.master.state.limit_txt: - r.append("[") - r.append(("heading_key", "l")) - r.append(":%s]"%self.master.state.limit_txt) - if self.master.stickycookie_txt: - r.append("[") - r.append(("heading_key", "t")) - r.append(":%s]"%self.master.stickycookie_txt) - if self.master.stickyauth_txt: - r.append("[") - r.append(("heading_key", "u")) - r.append(":%s]"%self.master.stickyauth_txt) - if self.master.state.default_body_view.name != "Auto": - r.append("[") - r.append(("heading_key", "M")) - r.append(":%s]"%self.master.state.default_body_view.name) - - opts = [] - if self.master.anticache: - opts.append("anticache") - if self.master.anticomp: - opts.append("anticomp") - if self.master.showhost: - opts.append("showhost") - if not self.master.refresh_server_playback: - opts.append("norefresh") - if self.master.killextra: - opts.append("killextra") - if self.master.server.config.no_upstream_cert: - opts.append("no-upstream-cert") - if self.master.state.follow_focus: - opts.append("following") - if self.master.stream_large_bodies: - opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) - - if opts: - r.append("[%s]"%(":".join(opts))) - - if self.master.server.config.mode in ["reverse", "upstream"]: - dst = self.master.server.config.mode.dst - scheme = "https" if dst[0] else "http" - if dst[1] != dst[0]: - scheme += "2https" if dst[1] else "http" - r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:])) - if self.master.scripts: - r.append("[") - r.append(("heading_key", "s")) - r.append("cripts:%s]"%len(self.master.scripts)) - # r.append("[lt:%0.3f]"%self.master.looptime) - - if self.master.stream: - r.append("[W:%s]"%self.master.stream_path) - - return r - - def redraw(self): - if self.ab.expire and time.time() > self.ab.expire: - self.message("") - - fc = self.master.state.flow_count() - if self.master.state.focus is None: - offset = 0 - else: - offset = min(self.master.state.focus + 1, fc) - t = [ - ('heading', ("[%s/%s]"%(offset, fc)).ljust(9)) - ] - - if self.master.server.bound: - host = self.master.server.address.host - if host == "0.0.0.0": - host = "*" - boundaddr = "[%s:%s]"%(host, self.master.server.address.port) - else: - boundaddr = "" - t.extend(self.get_status()) - status = urwid.AttrWrap(urwid.Columns([ - urwid.Text(t), - urwid.Text( - [ - self.helptext, - boundaddr - ], - align="right" - ), - ]), "heading") - self.ib._w = status - - def update(self, text): - self.helptext = text - self.redraw() - self.master.loop.draw_screen() - - def selectable(self): - return True - - def get_edit_text(self): - return self.ab._w.get_edit_text() - - def path_prompt(self, prompt, text): - return self.ab.path_prompt(prompt, text) - - def prompt(self, prompt, text = ""): - self.ab.prompt(prompt, text) - - def message(self, msg, expire=None): - if expire: - expire = time.time() + float(expire)/1000 - self.ab.message(msg, expire) - self.master.loop.draw_screen() - - class ConsoleState(flow.State): def __init__(self): flow.State.__init__(self) @@ -763,7 +588,7 @@ class ConsoleMaster(flow.FlowMaster): self.help_context, (self.statusbar, self.body, self.header) ) - self.statusbar = StatusBar(self, help.footer) + self.statusbar = statusbar.StatusBar(self, help.footer) self.body = h self.header = None self.loop.widget = self.make_view() @@ -774,7 +599,7 @@ class ConsoleMaster(flow.FlowMaster): flow, (self.statusbar, self.body, self.header) ) - self.statusbar = StatusBar(self, flowdetailview.footer) + self.statusbar = statusbar.StatusBar(self, flowdetailview.footer) self.body = h self.header = None self.loop.widget = self.make_view() @@ -783,7 +608,7 @@ class ConsoleMaster(flow.FlowMaster): self.body = ge self.header = None self.help_context = ge.make_help() - self.statusbar = StatusBar(self, grideditor.footer) + self.statusbar = statusbar.StatusBar(self, grideditor.footer) self.loop.widget = self.make_view() def view_flowlist(self): @@ -796,7 +621,7 @@ class ConsoleMaster(flow.FlowMaster): self.body = flowlist.BodyPile(self) else: self.body = flowlist.FlowListBox(self) - self.statusbar = StatusBar(self, flowlist.footer) + self.statusbar = statusbar.StatusBar(self, flowlist.footer) self.header = None self.state.view_mode = common.VIEW_LIST @@ -806,7 +631,7 @@ class ConsoleMaster(flow.FlowMaster): def view_flow(self, flow): self.body = flowview.FlowView(self, self.state, flow) self.header = flowview.FlowViewHeader(self, flow) - self.statusbar = StatusBar(self, flowview.footer) + self.statusbar = statusbar.StatusBar(self, flowview.footer) self.state.set_focus_flow(flow) self.state.view_mode = common.VIEW_FLOW self.loop.widget = self.make_view() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py new file mode 100644 index 00000000..4fb717cd --- /dev/null +++ b/libmproxy/console/statusbar.py @@ -0,0 +1,180 @@ + +import time + +import urwid + +from . import pathedit +from .. import utils + + +class ActionBar(urwid.WidgetWrap): + def __init__(self): + self.message("") + + def selectable(self): + return True + + def path_prompt(self, prompt, text): + self.expire = None + self._w = pathedit.PathEdit(prompt, text) + + def prompt(self, prompt, text = ""): + self.expire = None + self._w = urwid.Edit(prompt, text or "") + + def message(self, message, expire=None): + self.expire = expire + self._w = urwid.Text(message) + + +class StatusBar(urwid.WidgetWrap): + def __init__(self, master, helptext): + self.master, self.helptext = master, helptext + self.ab = ActionBar() + self.ib = urwid.WidgetWrap(urwid.Text("")) + self._w = urwid.Pile([self.ib, self.ab]) + + def get_status(self): + r = [] + + if self.master.setheaders.count(): + r.append("[") + r.append(("heading_key", "H")) + r.append("eaders]") + if self.master.replacehooks.count(): + r.append("[") + r.append(("heading_key", "R")) + r.append("eplacing]") + if self.master.client_playback: + r.append("[") + r.append(("heading_key", "cplayback")) + r.append(":%s to go]"%self.master.client_playback.count()) + if self.master.server_playback: + r.append("[") + r.append(("heading_key", "splayback")) + if self.master.nopop: + r.append(":%s in file]"%self.master.server_playback.count()) + else: + r.append(":%s to go]"%self.master.server_playback.count()) + if self.master.get_ignore_filter(): + r.append("[") + r.append(("heading_key", "I")) + r.append("gnore:%d]" % len(self.master.get_ignore_filter())) + if self.master.get_tcp_filter(): + r.append("[") + r.append(("heading_key", "T")) + r.append("CP:%d]" % len(self.master.get_tcp_filter())) + if self.master.state.intercept_txt: + r.append("[") + r.append(("heading_key", "i")) + r.append(":%s]"%self.master.state.intercept_txt) + if self.master.state.limit_txt: + r.append("[") + r.append(("heading_key", "l")) + r.append(":%s]"%self.master.state.limit_txt) + if self.master.stickycookie_txt: + r.append("[") + r.append(("heading_key", "t")) + r.append(":%s]"%self.master.stickycookie_txt) + if self.master.stickyauth_txt: + r.append("[") + r.append(("heading_key", "u")) + r.append(":%s]"%self.master.stickyauth_txt) + if self.master.state.default_body_view.name != "Auto": + r.append("[") + r.append(("heading_key", "M")) + r.append(":%s]"%self.master.state.default_body_view.name) + + opts = [] + if self.master.anticache: + opts.append("anticache") + if self.master.anticomp: + opts.append("anticomp") + if self.master.showhost: + opts.append("showhost") + if not self.master.refresh_server_playback: + opts.append("norefresh") + if self.master.killextra: + opts.append("killextra") + if self.master.server.config.no_upstream_cert: + opts.append("no-upstream-cert") + if self.master.state.follow_focus: + opts.append("following") + if self.master.stream_large_bodies: + opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) + + if opts: + r.append("[%s]"%(":".join(opts))) + + if self.master.server.config.mode in ["reverse", "upstream"]: + dst = self.master.server.config.mode.dst + scheme = "https" if dst[0] else "http" + if dst[1] != dst[0]: + scheme += "2https" if dst[1] else "http" + r.append("[dest:%s]"%utils.unparse_url(scheme, *dst[2:])) + if self.master.scripts: + r.append("[") + r.append(("heading_key", "s")) + r.append("cripts:%s]"%len(self.master.scripts)) + # r.append("[lt:%0.3f]"%self.master.looptime) + + if self.master.stream: + r.append("[W:%s]"%self.master.stream_path) + + return r + + def redraw(self): + if self.ab.expire and time.time() > self.ab.expire: + self.message("") + + fc = self.master.state.flow_count() + if self.master.state.focus is None: + offset = 0 + else: + offset = min(self.master.state.focus + 1, fc) + t = [ + ('heading', ("[%s/%s]"%(offset, fc)).ljust(9)) + ] + + if self.master.server.bound: + host = self.master.server.address.host + if host == "0.0.0.0": + host = "*" + boundaddr = "[%s:%s]"%(host, self.master.server.address.port) + else: + boundaddr = "" + t.extend(self.get_status()) + status = urwid.AttrWrap(urwid.Columns([ + urwid.Text(t), + urwid.Text( + [ + self.helptext, + boundaddr + ], + align="right" + ), + ]), "heading") + self.ib._w = status + + def update(self, text): + self.helptext = text + self.redraw() + self.master.loop.draw_screen() + + def selectable(self): + return True + + def get_edit_text(self): + return self.ab._w.get_edit_text() + + def path_prompt(self, prompt, text): + return self.ab.path_prompt(prompt, text) + + def prompt(self, prompt, text = ""): + self.ab.prompt(prompt, text) + + def message(self, msg, expire=None): + if expire: + expire = time.time() + float(expire)/1000 + self.ab.message(msg, expire) + self.master.loop.draw_screen() -- cgit v1.2.3 From c182133d645a07b7dee4504ecf6f99cc3f72f93a Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 13:26:08 +1300 Subject: console: pull primary window frame management out into window.py --- libmproxy/console/__init__.py | 152 ++--------------------------------------- libmproxy/console/statusbar.py | 3 + libmproxy/console/window.py | 150 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 148 deletions(-) create mode 100644 libmproxy/console/window.py diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 5f564a20..426dda58 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,7 +15,7 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, common +from . import flowlist, flowview, help, common, window from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 @@ -146,7 +146,6 @@ class ConsoleMaster(flow.FlowMaster): def __init__(self, server, options): flow.FlowMaster.__init__(self, server, ConsoleState()) - self.looptime = 0 self.stream_path = None self.options = options @@ -363,149 +362,6 @@ class ConsoleMaster(flow.FlowMaster): def set_palette(self, name): self.palette = palettes.palettes[name] - def input_filter(self, keys, raw): - for k in keys: - if self.prompting: - if k == "esc": - self.prompt_cancel() - elif self.onekey: - if k == "enter": - self.prompt_cancel() - elif k in self.onekey: - self.prompt_execute(k) - elif k == "enter": - self.prompt_execute() - else: - if common.is_keypress(k): - self.view.keypress(self.loop.screen_size, k) - else: - k = self.view.keypress(self.loop.screen_size, k) - if k: - self.statusbar.message("") - if k == "?": - self.view_help() - elif k == "c": - if not self.client_playback: - self.path_prompt( - "Client replay: ", - self.state.last_saveload, - self.client_playback_path - ) - else: - self.prompt_onekey( - "Stop current client replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.stop_client_playback_prompt, - ) - elif k == "H": - self.view_grideditor( - grideditor.SetHeadersEditor( - self, - self.setheaders.get_specs(), - self.setheaders.set - ) - ) - elif k == "I": - self.view_grideditor( - grideditor.HostPatternEditor( - self, - [[x] for x in self.get_ignore_filter()], - self.edit_ignore_filter - ) - ) - elif k == "T": - self.view_grideditor( - grideditor.HostPatternEditor( - self, - [[x] for x in self.get_tcp_filter()], - self.edit_tcp_filter - ) - ) - elif k == "i": - self.prompt( - "Intercept filter: ", - self.state.intercept_txt, - self.set_intercept - ) - elif k == "Q": - raise urwid.ExitMainLoop - elif k == "q": - self.prompt_onekey( - "Quit", - ( - ("yes", "y"), - ("no", "n"), - ), - self.quit, - ) - elif k == "M": - self.prompt_onekey( - "Global default display mode", - contentview.view_prompts, - self.change_default_display_mode - ) - elif k == "R": - self.view_grideditor( - grideditor.ReplaceEditor( - self, - self.replacehooks.get_specs(), - self.replacehooks.set - ) - ) - elif k == "s": - self.view_grideditor( - grideditor.ScriptEditor( - self, - [[i.command] for i in self.scripts], - self.edit_scripts - ) - ) - elif k == "S": - if not self.server_playback: - self.path_prompt( - "Server replay path: ", - self.state.last_saveload, - self.server_playback_path - ) - else: - self.prompt_onekey( - "Stop current server replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.stop_server_playback_prompt, - ) - elif k == "o": - self.prompt_onekey( - "Options", - ( - ("anticache", "a"), - ("anticomp", "c"), - ("showhost", "h"), - ("killextra", "k"), - ("norefresh", "n"), - ("no-upstream-certs", "u"), - ), - self._change_options - ) - elif k == "t": - self.prompt( - "Sticky cookie filter: ", - self.stickycookie_txt, - self.set_stickycookie - ) - elif k == "u": - self.prompt( - "Sticky auth filter: ", - self.stickyauth_txt, - self.set_stickyauth - ) - self.statusbar.redraw() - def ticker(self, *userdata): changed = self.tick(self.masterq, timeout=0) if changed: @@ -528,7 +384,6 @@ class ConsoleMaster(flow.FlowMaster): self.loop = urwid.MainLoop( self.view, screen = self.ui, - input_filter = self.input_filter ) self.view_flowlist() self.statusbar.redraw() @@ -574,12 +429,13 @@ class ConsoleMaster(flow.FlowMaster): self.shutdown() def make_view(self): - self.view = urwid.Frame( + self.view = window.Window( + self, self.body, header = self.header, footer = self.statusbar ) - self.view.set_focus("body") + self.statusbar.redraw() return self.view def view_help(self): diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 4fb717cd..a38615b4 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -34,6 +34,9 @@ class StatusBar(urwid.WidgetWrap): self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) + def keypress(self, *args, **kwargs): + return self.ab.keypress(*args, **kwargs) + def get_status(self): r = [] diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py new file mode 100644 index 00000000..8019adce --- /dev/null +++ b/libmproxy/console/window.py @@ -0,0 +1,150 @@ +import urwid + +class Window(urwid.Frame): + def __init__(self, master, body, header, footer): + urwid.Frame.__init__(self, body, header=header, footer=footer) + self.master = master + + def keypress(self, size, k): + if self.master.prompting: + if k == "esc": + self.master.prompt_cancel() + elif self.master.onekey: + if k == "enter": + self.master.prompt_cancel() + elif k in self.master.onekey: + self.master.prompt_execute(k) + elif k == "enter": + self.master.prompt_execute() + else: + if common.is_keypress(k): + urwid.Frame.keypress(self, self.master.loop.screen_size, k) + else: + return k + else: + k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) + if k == "?": + self.master.view_help() + elif k == "c": + if not self.master.client_playback: + self.master.path_prompt( + "Client replay: ", + self.master.state.last_saveload, + self.master.client_playback_path + ) + else: + self.master.prompt_onekey( + "Stop current client replay?", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.stop_client_playback_prompt, + ) + elif k == "H": + self.master.view_grideditor( + grideditor.SetHeadersEditor( + self.master, + self.master.setheaders.get_specs(), + self.master.setheaders.set + ) + ) + elif k == "I": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_ignore_filter()], + self.master.edit_ignore_filter + ) + ) + elif k == "T": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_tcp_filter()], + self.master.edit_tcp_filter + ) + ) + elif k == "i": + self.master.prompt( + "Intercept filter: ", + self.master.state.intercept_txt, + self.master.set_intercept + ) + elif k == "Q": + raise urwid.ExitMainLoop + elif k == "q": + self.master.prompt_onekey( + "Quit", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.quit, + ) + elif k == "M": + self.master.prompt_onekey( + "Global default display mode", + contentview.view_prompts, + self.master.change_default_display_mode + ) + elif k == "R": + self.master.view_grideditor( + grideditor.ReplaceEditor( + self.master, + self.master.replacehooks.get_specs(), + self.master.replacehooks.set + ) + ) + elif k == "s": + self.master.view_grideditor( + grideditor.ScriptEditor( + self.master, + [[i.command] for i in self.master.scripts], + self.master.edit_scripts + ) + ) + elif k == "S": + if not self.master.server_playback: + self.master.path_prompt( + "Server replay path: ", + self.master.state.last_saveload, + self.master.server_playback_path + ) + else: + self.master.prompt_onekey( + "Stop current server replay?", + ( + ("yes", "y"), + ("no", "n"), + ), + self.master.stop_server_playback_prompt, + ) + elif k == "o": + self.master.prompt_onekey( + "Options", + ( + ("anticache", "a"), + ("anticomp", "c"), + ("showhost", "h"), + ("killextra", "k"), + ("norefresh", "n"), + ("no-upstream-certs", "u"), + ), + self.master._change_options + ) + elif k == "t": + self.master.prompt( + "Sticky cookie filter: ", + self.master.stickycookie_txt, + self.master.set_stickycookie + ) + elif k == "u": + self.master.prompt( + "Sticky auth filter: ", + self.master.stickyauth_txt, + self.master.set_stickyauth + ) + else: + return k + self.footer.redraw() -- cgit v1.2.3 From b475c8d6eacd0d6a100cf6aaddc9c9915fdfb149 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 20 Mar 2015 15:22:05 +1300 Subject: Add window.py import missed in refactoring --- libmproxy/console/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 8019adce..69f35183 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,4 +1,5 @@ import urwid +from . import common class Window(urwid.Frame): def __init__(self, master, body, header, footer): -- cgit v1.2.3 From 8725d50d03cf21b37a78c1d2fa03ade055c8a821 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 21 Mar 2015 11:19:20 +1300 Subject: Add blinker dependency, start using it to refactor console app Blinker lets us set up a central pub/sub mechanism to disentangle our object structure. --- libmproxy/console/__init__.py | 22 ++++++++++++---------- libmproxy/console/common.py | 11 ++++++----- libmproxy/console/flowlist.py | 10 +++++----- libmproxy/console/flowview.py | 24 ++++++++++++------------ libmproxy/console/grideditor.py | 8 ++++---- libmproxy/console/signals.py | 4 ++++ libmproxy/console/statusbar.py | 19 +++++-------------- libmproxy/console/window.py | 2 +- setup.py | 3 ++- 9 files changed, 51 insertions(+), 52 deletions(-) create mode 100644 libmproxy/console/signals.py diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 426dda58..b5c59ecf 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -15,7 +15,7 @@ import urwid import weakref from .. import controller, flow, script -from . import flowlist, flowview, help, common, window +from . import flowlist, flowview, help, common, window, signals from . import grideditor, palettes, contentview, flowdetailview, statusbar EVENTLOG_SIZE = 500 @@ -238,7 +238,9 @@ class ConsoleMaster(flow.FlowMaster): try: s = script.Script(command, self) except script.ScriptError, v: - self.statusbar.message("Error loading script.") + signals.status_message.send( + message = "Error loading script." + ) self.add_event("Error loading script:\n%s"%v.args[0], "error") return @@ -257,7 +259,7 @@ class ConsoleMaster(flow.FlowMaster): return ret = self.load_script(command) if ret: - self.statusbar.message(ret) + signals.status_message.send(message=ret) self.state.last_script = command def toggle_eventlog(self): @@ -279,7 +281,7 @@ class ConsoleMaster(flow.FlowMaster): print >> sys.stderr, e.strerror sys.exit(1) else: - self.statusbar.message(e.strerror) + signals.status_message.send(message=e.strerror) return None def client_playback_path(self, path): @@ -314,7 +316,7 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd) except: - self.statusbar.message("Can't start editor: %s" % " ".join(c)) + signals.status_message.send(message="Can't start editor: %s" % " ".join(c)) else: data = open(name, "rb").read() self.ui.start() @@ -353,8 +355,8 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd, shell=shell) except: - self.statusbar.message( - "Can't start external viewer: %s" % " ".join(c) + signals.status_message.send( + message="Can't start external viewer: %s" % " ".join(c) ) self.ui.start() os.unlink(name) @@ -505,7 +507,7 @@ class ConsoleMaster(flow.FlowMaster): fw.add(i) f.close() except IOError, v: - self.statusbar.message(v.strerror) + signals.status_message.send(message=v.strerror) def save_one_flow(self, path, flow): return self._write_flows(path, [flow]) @@ -565,7 +567,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompting = False self.onekey = False self.view.set_focus("body") - self.statusbar.message("") + signals.status_message.send(message="") def prompt_execute(self, txt=None): if not txt: @@ -574,7 +576,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_done() msg = p(txt, *args) if msg: - self.statusbar.message(msg, 1000) + signals.status_message.send(message=msg, expire=1000) def prompt_cancel(self): self.prompt_done() diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 90204d79..9731b682 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -6,6 +6,7 @@ import os from .. import utils from ..protocol.http import CONTENT_MISSING, decoded +from . import signals try: import pyperclip @@ -198,7 +199,7 @@ def save_data(path, data, master, state): with file(path, "wb") as f: f.write(data) except IOError, v: - master.statusbar.message(v.strerror) + signals.status_message.send(message=v.strerror) def ask_save_path(prompt, data, master, state): @@ -248,11 +249,11 @@ def copy_flow(part, scope, flow, master, state): if not data: if scope == "q": - master.statusbar.message("No request content to copy.") + signals.status_message.send(message="No request content to copy.") elif scope == "s": - master.statusbar.message("No response content to copy.") + signals.status_message.send(message="No response content to copy.") else: - master.statusbar.message("No contents to copy.") + signals.status_message.send(message="No contents to copy.") return try: @@ -336,7 +337,7 @@ def ask_save_body(part, master, state, flow): state ) else: - master.statusbar.message("No content to save.") + signals.status_message.send(message="No content to save.") class FlowCache: diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index 5d8ad942..c8ecf15c 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import urwid from netlib import http -from . import common +from . import common, signals def _mkhelp(): @@ -171,7 +171,7 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "r": r = self.master.replay_request(self.flow) if r: - self.master.statusbar.message(r) + signals.status_message.send(message=r) self.master.sync_list_view() elif key == "S": if not self.master.server_playback: @@ -195,11 +195,11 @@ class ConnectionItem(urwid.WidgetWrap): ) elif key == "V": if not self.flow.modified(): - self.master.statusbar.message("Flow not modified.") + signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) self.master.sync_list_view() - self.master.statusbar.message("Reverted.") + signals.status_message.send(message="Reverted.") elif key == "w": self.master.prompt_onekey( "Save", @@ -285,7 +285,7 @@ class FlowListBox(urwid.ListBox): def new_request(self, url, method): parts = http.parse_url(str(url)) if not parts: - self.master.statusbar.message("Invalid Url") + signals.status_message.send(message="Invalid Url") return scheme, host, port, path = parts f = self.master.create_request(method, scheme, host, port, path) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 89e75aad..b22bbb37 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -1,7 +1,7 @@ from __future__ import absolute_import import os, sys, copy import urwid -from . import common, grideditor, contentview +from . import common, grideditor, contentview, signals from .. import utils, flow, controller from ..protocol.http import HTTPRequest, HTTPResponse, CONTENT_MISSING, decoded @@ -282,10 +282,10 @@ class FlowView(urwid.WidgetWrap): if last_search_string: message = self.search(last_search_string, backwards) if message: - self.master.statusbar.message(message) + signals.status_message.send(message=message) else: message = "no previous searches have been made" - self.master.statusbar.message(message) + signals.status_message.send(message=message) return message @@ -606,7 +606,7 @@ class FlowView(urwid.WidgetWrap): else: new_flow, new_idx = self.state.get_prev(idx) if new_flow is None: - self.master.statusbar.message("No more flows!") + signals.status_message.send(message="No more flows!") return self.master.view_flow(new_flow) @@ -681,7 +681,7 @@ class FlowView(urwid.WidgetWrap): elif key == "D": f = self.master.duplicate_flow(self.flow) self.master.view_flow(f) - self.master.statusbar.message("Duplicated.") + signals.status_message.send(message="Duplicated.") elif key == "e": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: self.master.prompt_onekey( @@ -710,14 +710,14 @@ class FlowView(urwid.WidgetWrap): ) key = None elif key == "f": - self.master.statusbar.message("Loading all body data...") + signals.status_message.send(message="Loading all body data...") self.state.add_flow_setting( self.flow, (self.state.view_flow_mode, "fullcontents"), True ) self.master.refresh_flow(self.flow) - self.master.statusbar.message("") + signals.status_message.send(message="") elif key == "g": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: scope = "q" @@ -738,15 +738,15 @@ class FlowView(urwid.WidgetWrap): elif key == "r": r = self.master.replay_request(self.flow) if r: - self.master.statusbar.message(r) + signals.status_message.send(message=r) self.master.refresh_flow(self.flow) elif key == "V": if not self.flow.modified(): - self.master.statusbar.message("Flow not modified.") + signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) self.master.refresh_flow(self.flow) - self.master.statusbar.message("Reverted.") + signals.status_message.send(message="Reverted.") elif key == "W": self.master.path_prompt( "Save this flow: ", @@ -761,7 +761,7 @@ class FlowView(urwid.WidgetWrap): if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): self.master.spawn_external_viewer(conn.content, t) else: - self.master.statusbar.message("Error! Set $EDITOR or $PAGER.") + signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": self.master.path_prompt( "Send flow to script: ", self.state.last_script, @@ -785,7 +785,7 @@ class FlowView(urwid.WidgetWrap): e = conn.headers.get_first("content-encoding", "identity") if e != "identity": if not conn.decode(): - self.master.statusbar.message("Could not decode - invalid data?") + signals.status_message.send(message="Could not decode - invalid data?") else: self.master.prompt_onekey( "Select encoding: ", diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index fe3df509..2d2754b1 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -5,7 +5,7 @@ import re import os import urwid -from . import common +from . import common, signals from .. import utils, filt, script from netlib import http_uastrings @@ -125,14 +125,14 @@ class GridWalker(urwid.ListWalker): try: val = val.decode("string-escape") except ValueError: - self.editor.master.statusbar.message( - "Invalid Python-style string encoding.", 1000 + signals.status_message.send( + self, message = "Invalid Python-style string encoding.", expure = 1000 ) return errors = self.lst[self.focus][1] emsg = self.editor.is_error(self.focus_col, val) if emsg: - self.editor.master.statusbar.message(emsg, 1000) + signals.status_message.send(message = emsg, expire = 1000) errors.add(self.focus_col) else: errors.discard(self.focus_col) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py new file mode 100644 index 00000000..a844ef8f --- /dev/null +++ b/libmproxy/console/signals.py @@ -0,0 +1,4 @@ + +import blinker + +status_message = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index a38615b4..7ad78f03 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -3,26 +3,26 @@ import time import urwid -from . import pathedit +from . import pathedit, signals from .. import utils + class ActionBar(urwid.WidgetWrap): def __init__(self): - self.message("") + urwid.WidgetWrap.__init__(self, urwid.Text("")) + signals.status_message.connect(self.message) def selectable(self): return True def path_prompt(self, prompt, text): - self.expire = None self._w = pathedit.PathEdit(prompt, text) def prompt(self, prompt, text = ""): - self.expire = None self._w = urwid.Edit(prompt, text or "") - def message(self, message, expire=None): + def message(self, sender, message, expire=None): self.expire = expire self._w = urwid.Text(message) @@ -127,9 +127,6 @@ class StatusBar(urwid.WidgetWrap): return r def redraw(self): - if self.ab.expire and time.time() > self.ab.expire: - self.message("") - fc = self.master.state.flow_count() if self.master.state.focus is None: offset = 0 @@ -175,9 +172,3 @@ class StatusBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self.ab.prompt(prompt, text) - - def message(self, msg, expire=None): - if expire: - expire = time.time() + float(expire)/1000 - self.ab.message(msg, expire) - self.master.loop.draw_screen() diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 69f35183..44a5a316 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,5 +1,5 @@ import urwid -from . import common +from . import common, grideditor class Window(urwid.Frame): def __init__(self, master, body, header, footer): diff --git a/setup.py b/setup.py index fd3e6657..bffce805 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ deps = { "pyOpenSSL>=0.14", "tornado>=4.0.2", "configargparse>=0.9.3", - "pyperclip>=1.5.8" + "pyperclip>=1.5.8", + "blinker>=1.3" } script_deps = { "mitmproxy": { -- cgit v1.2.3 From 381a56306777900153939b1b46f20e63322944c2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 21 Mar 2015 12:37:00 +1300 Subject: Status bar message expiry based on signals and Urwid main loop --- libmproxy/console/__init__.py | 8 +++++++- libmproxy/console/grideditor.py | 2 +- libmproxy/console/signals.py | 1 + libmproxy/console/statusbar.py | 21 ++++++++++++++------- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index b5c59ecf..aae7a9c4 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -212,6 +212,12 @@ class ConsoleMaster(flow.FlowMaster): if options.app: self.start_app(self.options.app_host, self.options.app_port) + signals.call_in.connect(self.sig_call_in) + + def sig_call_in(self, sender, seconds, callback, args=()): + def cb(*_): + return callback(*args) + self.loop.set_alarm_in(seconds, cb) def start_stream_to_path(self, path, mode="wb"): path = os.path.expanduser(path) @@ -576,7 +582,7 @@ class ConsoleMaster(flow.FlowMaster): self.prompt_done() msg = p(txt, *args) if msg: - signals.status_message.send(message=msg, expire=1000) + signals.status_message.send(message=msg, expire=1) def prompt_cancel(self): self.prompt_done() diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 2d2754b1..0b563c52 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -132,7 +132,7 @@ class GridWalker(urwid.ListWalker): errors = self.lst[self.focus][1] emsg = self.editor.is_error(self.focus_col, val) if emsg: - signals.status_message.send(message = emsg, expire = 1000) + signals.status_message.send(message = emsg, expire = 1) errors.add(self.focus_col) else: errors.discard(self.focus_col) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index a844ef8f..7b0ec937 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -2,3 +2,4 @@ import blinker status_message = blinker.Signal() +call_in = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7ad78f03..a29767e4 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -1,4 +1,3 @@ - import time import urwid @@ -7,11 +6,14 @@ from . import pathedit, signals from .. import utils - class ActionBar(urwid.WidgetWrap): def __init__(self): - urwid.WidgetWrap.__init__(self, urwid.Text("")) - signals.status_message.connect(self.message) + urwid.WidgetWrap.__init__(self, None) + self.clear() + signals.status_message.connect(self.sig_message) + + def clear(self): + self._w = urwid.Text("") def selectable(self): return True @@ -22,9 +24,14 @@ class ActionBar(urwid.WidgetWrap): def prompt(self, prompt, text = ""): self._w = urwid.Edit(prompt, text or "") - def message(self, sender, message, expire=None): - self.expire = expire - self._w = urwid.Text(message) + def sig_message(self, sender, message, expire=None): + w = urwid.Text(message) + self._w = w + if expire: + def cb(*args): + if w == self._w: + self.clear() + signals.call_in.send(seconds=expire, callback=cb) class StatusBar(urwid.WidgetWrap): -- cgit v1.2.3 From ac5d74d42c0824b5789cc030bf39a447951e4804 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 21:55:02 +0100 Subject: web: fix bugs --- libmproxy/web/static/app.js | 17 ++++++++++------- web/src/js/actions.js | 1 + web/src/js/components/eventlog.js | 1 + web/src/js/components/header.js | 2 +- web/src/js/components/mainview.js | 2 ++ web/src/js/store/view.js | 8 +++----- web/src/js/utils.js | 1 + 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index dae10a34..ef53c08f 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -421,6 +421,7 @@ module.exports = { ConnectionActions: ConnectionActions, FlowActions: FlowActions, StoreCmds: StoreCmds, + SettingsActions: SettingsActions, Query: Query }; @@ -622,6 +623,7 @@ var common = require("./common.js"); var Query = require("../actions.js").Query; var VirtualScrollMixin = require("./virtualscroll.js"); var views = require("../store/view.js"); +var _ = require("lodash"); var LogMessage = React.createClass({displayName: "LogMessage", render: function () { @@ -775,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1774,7 +1776,7 @@ var MainMenu = React.createClass({displayName: "MainMenu", this.setQuery(d); }, onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); + actions.SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; @@ -2196,6 +2198,8 @@ var MainView = React.createClass({displayName: "MainView", actions.FlowActions.revert(flow); } break; + case toputils.Key.SHIFT: + break; default: console.debug("keydown", e.keyCode); return; @@ -4514,8 +4518,6 @@ var default_filt = function (elem) { function StoreView(store, filt, sortfun) { EventEmitter.call(this); - filt = filt || default_filt; - sortfun = sortfun || default_sort; this.store = store; @@ -4539,10 +4541,10 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (filt, sortfun) { - filt = filt || default_filt; - sortfun = sortfun || default_sort; + filt = filt || this.filt || default_filt; + sortfun = sortfun || this.sortfun || default_sort; filt = filt.bind(this); - sortfun = sortfun.bind(this) + sortfun = sortfun.bind(this); this.filt = filt; this.sortfun = sortfun; @@ -4633,6 +4635,7 @@ var Key = { TAB: 9, SPACE: 32, BACKSPACE: 8, + SHIFT: 16 }; // Add A-Z for (var i = 65; i <= 90; i++) { diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 64cd68a7..1b29438c 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -117,5 +117,6 @@ module.exports = { ConnectionActions: ConnectionActions, FlowActions: FlowActions, StoreCmds: StoreCmds, + SettingsActions: SettingsActions, Query: Query }; \ No newline at end of file diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index 23508275..de69462b 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -3,6 +3,7 @@ var common = require("./common.js"); var Query = require("../actions.js").Query; var VirtualScrollMixin = require("./virtualscroll.js"); var views = require("../store/view.js"); +var _ = require("lodash"); var LogMessage = React.createClass({ render: function () { diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index d1684fb2..eca06e74 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -173,7 +173,7 @@ var MainMenu = React.createClass({ this.setQuery(d); }, onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); + actions.SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 184ef49f..8eda2e9c 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -203,6 +203,8 @@ var MainView = React.createClass({ actions.FlowActions.revert(flow); } break; + case toputils.Key.SHIFT: + break; default: console.debug("keydown", e.keyCode); return; diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js index 204d22da..d13822d5 100644 --- a/web/src/js/store/view.js +++ b/web/src/js/store/view.js @@ -14,8 +14,6 @@ var default_filt = function (elem) { function StoreView(store, filt, sortfun) { EventEmitter.call(this); - filt = filt || default_filt; - sortfun = sortfun || default_sort; this.store = store; @@ -39,10 +37,10 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (filt, sortfun) { - filt = filt || default_filt; - sortfun = sortfun || default_sort; + filt = filt || this.filt || default_filt; + sortfun = sortfun || this.sortfun || default_sort; filt = filt.bind(this); - sortfun = sortfun.bind(this) + sortfun = sortfun.bind(this); this.filt = filt; this.sortfun = sortfun; diff --git a/web/src/js/utils.js b/web/src/js/utils.js index bcd3958d..48f69880 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -15,6 +15,7 @@ var Key = { TAB: 9, SPACE: 32, BACKSPACE: 8, + SHIFT: 16 }; // Add A-Z for (var i = 65; i <= 90; i++) { -- cgit v1.2.3 From 02a61ea45dc1ca6d0c88b44adf83f68b791130e7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 22:49:51 +0100 Subject: structure components --- libmproxy/protocol/http.py | 2 + libmproxy/web/static/app.js | 1031 ++++++++++++++-------------- web/src/js/components/flowdetail.js | 399 ----------- web/src/js/components/flowview/details.js | 181 +++++ web/src/js/components/flowview/index.js | 74 ++ web/src/js/components/flowview/messages.js | 102 +++ web/src/js/components/flowview/nav.js | 61 ++ web/src/js/components/mainview.js | 4 +- web/src/js/flow/utils.js | 12 +- 9 files changed, 956 insertions(+), 910 deletions(-) delete mode 100644 web/src/js/components/flowdetail.js create mode 100644 web/src/js/components/flowview/details.js create mode 100644 web/src/js/components/flowview/index.js create mode 100644 web/src/js/components/flowview/messages.js create mode 100644 web/src/js/components/flowview/nav.js diff --git a/libmproxy/protocol/http.py b/libmproxy/protocol/http.py index 49310ec3..00086c21 100644 --- a/libmproxy/protocol/http.py +++ b/libmproxy/protocol/http.py @@ -119,6 +119,8 @@ class HTTPMessage(stateobject.StateObject): if short: if self.content: ret["contentLength"] = len(self.content) + elif self.content == CONTENT_MISSING: + ret["contentLength"] = None else: ret["contentLength"] = 0 return ret diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index ef53c08f..04d6f282 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":12,"./connection":14,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,156 +777,399 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); +var RequestUtils = require("../flow/utils.js").RequestUtils; +var ResponseUtils = require("../flow/utils.js").ResponseUtils; +var utils = require("../utils.js"); -var NavAction = React.createClass({displayName: "NavAction", - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); +var TLSColumn = React.createClass({displayName: "TLSColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); + } + }), + sortKeyFun: function(flow){ + return flow.request.scheme; + } }, render: function () { - return ( - React.createElement("a", {title: this.props.title, - href: "#", - className: "nav-action", - onClick: this.onClick}, - React.createElement("i", {className: "fa fa-fw " + this.props.icon}) - ) - ); + var flow = this.props.flow; + var ssl = (flow.request.scheme == "https"); + var classes; + if (ssl) { + classes = "col-tls col-tls-https"; + } else { + classes = "col-tls col-tls-http"; + } + return React.createElement("td", {className: classes}); } }); -var FlowDetailNav = React.createClass({displayName: "FlowDetailNav", + +var IconColumn = React.createClass({displayName: "IconColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); + } + }) + }, render: function () { var flow = this.props.flow; - var tabs = this.props.tabs.map(function (e) { - var str = e.charAt(0).toUpperCase() + e.slice(1); - var className = this.props.active === e ? "active" : ""; - var onClick = function (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return React.createElement("a", {key: e, - href: "#", - className: className, - onClick: onClick}, str); - }.bind(this)); + var icon; + if (flow.response) { + var contentType = ResponseUtils.getContentType(flow.response); - var acceptButton = null; - if(flow.intercepted){ - acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + //TODO: We should assign a type to the flow somewhere else. + if (flow.response.code == 304) { + icon = "resource-icon-not-modified"; + } else if (300 <= flow.response.code && flow.response.code < 400) { + icon = "resource-icon-redirect"; + } else if (contentType && contentType.indexOf("image") >= 0) { + icon = "resource-icon-image"; + } else if (contentType && contentType.indexOf("javascript") >= 0) { + icon = "resource-icon-js"; + } else if (contentType && contentType.indexOf("css") >= 0) { + icon = "resource-icon-css"; + } else if (contentType && contentType.indexOf("html") >= 0) { + icon = "resource-icon-document"; + } } - var revertButton = null; - if(flow.modified){ - revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + if (!icon) { + icon = "resource-icon-plain"; } - return ( - React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, - tabs, - React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), - React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), - React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), - acceptButton, - revertButton - ) + + icon += " resource-icon"; + return React.createElement("td", {className: "col-icon"}, + React.createElement("div", {className: icon}) ); } }); -var Headers = React.createClass({displayName: "Headers", +var PathColumn = React.createClass({displayName: "PathColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); + } + }), + sortKeyFun: function(flow){ + return RequestUtils.pretty_url(flow.request); + } + }, render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - React.createElement("tr", {key: i}, - React.createElement("td", {className: "header-name"}, header[0] + ":"), - React.createElement("td", {className: "header-value"}, header[1]) - ) - ); - }); - return ( - React.createElement("table", {className: "header-table"}, - React.createElement("tbody", null, - rows - ) - ) + var flow = this.props.flow; + return React.createElement("td", {className: "col-path"}, + flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, + flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, + RequestUtils.pretty_url(flow.request) ); } }); -var FlowDetailRequest = React.createClass({displayName: "FlowDetailRequest", + +var MethodColumn = React.createClass({displayName: "MethodColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); + } + }), + sortKeyFun: function(flow){ + return flow.request.method; + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); + return React.createElement("td", {className: "col-method"}, flow.request.method); + } +}); + + +var StatusColumn = React.createClass({displayName: "StatusColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); + } + }), + sortKeyFun: function(flow){ + return flow.response ? flow.response.code : undefined; + } + }, + render: function () { + var flow = this.props.flow; + var status; + if (flow.response) { + status = flow.response.code; } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + status = null; } + return React.createElement("td", {className: "col-status"}, status); + } +}); - //TODO: Styling - return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.request}), - React.createElement("hr", null), - content - ) - ); +var SizeColumn = React.createClass({displayName: "SizeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); + } + }), + sortKeyFun: function(flow){ + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + return total; + } + }, + render: function () { + var flow = this.props.flow; + + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + var size = utils.formatSize(total); + return React.createElement("td", {className: "col-size"}, size); } }); -var FlowDetailResponse = React.createClass({displayName: "FlowDetailResponse", + +var TimeColumn = React.createClass({displayName: "TimeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); + } + }), + sortKeyFun: function(flow){ + if(flow.response) { + return flow.response.timestamp_end - flow.request.timestamp_start; + } + } + }, render: function () { var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); + var time; + if (flow.response) { + time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + time = "..."; } + return React.createElement("td", {className: "col-time"}, time); + } +}); - //TODO: Styling + +var all_columns = [ + TLSColumn, + IconColumn, + PathColumn, + MethodColumn, + StatusColumn, + SizeColumn, + TimeColumn +]; + +module.exports = all_columns; + +},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +var React = require("react"); +var common = require("./common.js"); +var utils = require("../utils.js"); +var _ = require("lodash"); + +var VirtualScrollMixin = require("./virtualscroll.js"); +var flowtable_columns = require("./flowtable-columns.js"); + +var FlowRow = React.createClass({displayName: "FlowRow", + render: function () { + var flow = this.props.flow; + var columns = this.props.columns.map(function (Column) { + return React.createElement(Column, {key: Column.displayName, flow: flow}); + }.bind(this)); + var className = ""; + if (this.props.selected) { + className += " selected"; + } + if (this.props.highlighted) { + className += " highlighted"; + } + if (flow.intercepted) { + className += " intercepted"; + } + if (flow.request) { + className += " has-request"; + } + if (flow.response) { + className += " has-response"; + } return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.response}), - React.createElement("hr", null), - content - ) + React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, + columns + )); + }, + shouldComponentUpdate: function (nextProps) { + return true; + // Further optimization could be done here + // by calling forceUpdate on flow updates, selection changes and column changes. + //return ( + //(this.props.columns.length !== nextProps.columns.length) || + //(this.props.selected !== nextProps.selected) + //); + } +}); + +var FlowTableHead = React.createClass({displayName: "FlowTableHead", + getInitialState: function(){ + return { + sortColumn: undefined, + sortDesc: false + }; + }, + onClick: function(Column){ + var sortDesc = this.state.sortDesc; + var hasSort = Column.sortKeyFun; + if(Column === this.state.sortColumn){ + sortDesc = !sortDesc; + this.setState({ + sortDesc: sortDesc + }); + } else { + this.setState({ + sortColumn: hasSort && Column, + sortDesc: false + }) + } + var sortKeyFun; + if(!sortDesc){ + sortKeyFun = Column.sortKeyFun; + } else { + sortKeyFun = hasSort && function(){ + var k = Column.sortKeyFun.apply(this, arguments); + if(_.isString(k)){ + return utils.reverseString(""+k); + } else { + return -k; + } + } + } + this.props.setSortKeyFun(sortKeyFun); + }, + render: function () { + var columns = this.props.columns.map(function (Column) { + var onClick = this.onClick.bind(this, Column); + var className; + if(this.state.sortColumn === Column) { + if(this.state.sortDesc){ + className = "sort-desc"; + } else { + className = "sort-asc"; + } + } + return React.createElement(Column.Title, { + key: Column.displayName, + onClick: onClick, + className: className}); + }.bind(this)); + return React.createElement("thead", null, + React.createElement("tr", null, columns) + ); + } +}); + + +var ROW_HEIGHT = 32; + +var FlowTable = React.createClass({displayName: "FlowTable", + mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + columns: flowtable_columns + }; + }, + _listen: function(view){ + if(!view){ + return; + } + view.addListener("add", this.onChange); + view.addListener("update", this.onChange); + view.addListener("remove", this.onChange); + view.addListener("recalculate", this.onChange); + }, + componentWillMount: function () { + this._listen(this.props.view); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add"); + this.props.view.removeListener("update"); + this.props.view.removeListener("remove"); + this.props.view.removeListener("recalculate"); + } + this._listen(nextProps.view); + } + }, + getDefaultProps: function () { + return { + rowHeight: ROW_HEIGHT + }; + }, + onScrollFlowTable: function () { + this.adjustHead(); + this.onScroll(); + }, + onChange: function () { + this.forceUpdate(); + }, + scrollIntoView: function (flow) { + this.scrollRowIntoView( + this.props.view.index(flow), + this.refs.body.getDOMNode().offsetTop + ); + }, + renderRow: function (flow) { + var selected = (flow === this.props.selected); + var highlighted = + ( + this.props.view._highlight && + this.props.view._highlight[flow.id] + ); + + return React.createElement(FlowRow, {key: flow.id, + ref: flow.id, + flow: flow, + columns: this.state.columns, + selected: selected, + highlighted: highlighted, + selectFlow: this.props.selectFlow} ); - } -}); - -var FlowDetailError = React.createClass({displayName: "FlowDetailError", + }, render: function () { - var flow = this.props.flow; + //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); + var flows = this.props.view ? this.props.view.list : []; + + var rows = this.renderRows(flows); + return ( - React.createElement("section", null, - React.createElement("div", {className: "alert alert-warning"}, - flow.error.msg, - React.createElement("div", null, - React.createElement("small", null, toputils.formatTimeStamp(flow.error.timestamp) ) + React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, + React.createElement("table", null, + React.createElement(FlowTableHead, {ref: "head", + columns: this.state.columns, + setSortKeyFun: this.props.setSortKeyFun}), + React.createElement("tbody", {ref: "body"}, + this.getPlaceholderTop(flows.length), + rows, + this.getPlaceholderBottom(flows.length) ) ) ) @@ -934,6 +1177,15 @@ var FlowDetailError = React.createClass({displayName: "FlowDetailError", } }); +module.exports = FlowTable; + + +},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + var TimeStamp = React.createClass({displayName: "TimeStamp", render: function () { @@ -942,11 +1194,11 @@ var TimeStamp = React.createClass({displayName: "TimeStamp", return React.createElement("tr", null); } - var ts = toputils.formatTimeStamp(this.props.t); + var ts = utils.formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")"); } else { delta = null; @@ -1086,7 +1338,7 @@ var Timing = React.createClass({displayName: "Timing", } }); -var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnectionInfo", +var Details = React.createClass({displayName: "Details", render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; @@ -1109,14 +1361,25 @@ var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnec } }); +module.exports = Details; + +},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var common = require("../common.js"); +var Nav = require("./nav.js"); +var Messages = require("./messages.js"); +var Details = require("./details.js"); + var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo + request: Messages.Request, + response: Messages.Response, + error: Messages.Error, + details: Details }; -var FlowDetail = React.createClass({displayName: "FlowDetail", +var FlowView = React.createClass({displayName: "FlowView", mixins: [common.StickyHeadMixin, common.Navigation, common.State], getTabs: function (flow) { var tabs = []; @@ -1163,7 +1426,7 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", var Tab = allTabs[active]; return ( React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead}, - React.createElement(FlowDetailNav, {ref: "head", + React.createElement(Nav, {ref: "head", flow: flow, tabs: tabs, active: active, @@ -1174,414 +1437,176 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", } }); -module.exports = { - FlowDetail: FlowDetail -}; +module.exports = FlowView; -},{"../actions.js":2,"../flow/utils.js":17,"../utils.js":20,"./common.js":4,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){ +},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); -var RequestUtils = require("../flow/utils.js").RequestUtils; -var ResponseUtils = require("../flow/utils.js").ResponseUtils; -var utils = require("../utils.js"); - -var TLSColumn = React.createClass({displayName: "TLSColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); - } - }), - sortKeyFun: function(flow){ - return flow.request.scheme; - } - }, - render: function () { - var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); - var classes; - if (ssl) { - classes = "col-tls col-tls-https"; - } else { - classes = "col-tls col-tls-http"; - } - return React.createElement("td", {className: classes}); - } -}); - - -var IconColumn = React.createClass({displayName: "IconColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); - } - }) - }, - render: function () { - var flow = this.props.flow; - - var icon; - if (flow.response) { - var contentType = ResponseUtils.getContentType(flow.response); - - //TODO: We should assign a type to the flow somewhere else. - if (flow.response.code == 304) { - icon = "resource-icon-not-modified"; - } else if (300 <= flow.response.code && flow.response.code < 400) { - icon = "resource-icon-redirect"; - } else if (contentType && contentType.indexOf("image") >= 0) { - icon = "resource-icon-image"; - } else if (contentType && contentType.indexOf("javascript") >= 0) { - icon = "resource-icon-js"; - } else if (contentType && contentType.indexOf("css") >= 0) { - icon = "resource-icon-css"; - } else if (contentType && contentType.indexOf("html") >= 0) { - icon = "resource-icon-document"; - } - } - if (!icon) { - icon = "resource-icon-plain"; - } - - icon += " resource-icon"; - return React.createElement("td", {className: "col-icon"}, - React.createElement("div", {className: icon}) - ); - } -}); +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); -var PathColumn = React.createClass({displayName: "PathColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); - } - }), - sortKeyFun: function(flow){ - return RequestUtils.pretty_url(flow.request); - } - }, +var Headers = React.createClass({displayName: "Headers", render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-path"}, - flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, - flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, - RequestUtils.pretty_url(flow.request) + var rows = this.props.message.headers.map(function (header, i) { + return ( + React.createElement("tr", {key: i}, + React.createElement("td", {className: "header-name"}, header[0] + ":"), + React.createElement("td", {className: "header-value"}, header[1]) + ) + ); + }); + return ( + React.createElement("table", {className: "header-table"}, + React.createElement("tbody", null, + rows + ) + ) ); } }); - -var MethodColumn = React.createClass({displayName: "MethodColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); - } - }), - sortKeyFun: function(flow){ - return flow.request.method; - } - }, - render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-method"}, flow.request.method); - } -}); - - -var StatusColumn = React.createClass({displayName: "StatusColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); - } - }), - sortKeyFun: function(flow){ - return flow.response ? flow.response.code : undefined; - } - }, - render: function () { - var flow = this.props.flow; - var status; - if (flow.response) { - status = flow.response.code; - } else { - status = null; - } - return React.createElement("td", {className: "col-status"}, status); - } -}); - - -var SizeColumn = React.createClass({displayName: "SizeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); - } - }), - sortKeyFun: function(flow){ - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - return total; - } - }, - render: function () { - var flow = this.props.flow; - - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - var size = utils.formatSize(total); - return React.createElement("td", {className: "col-size"}, size); - } -}); - - -var TimeColumn = React.createClass({displayName: "TimeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); - } - }), - sortKeyFun: function(flow){ - if(flow.response) { - return flow.response.timestamp_end - flow.request.timestamp_start; - } - } - }, +var Request = React.createClass({displayName: "Request", render: function () { var flow = this.props.flow; - var time; - if (flow.response) { - time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); + var first_line = [ + flow.request.method, + flowutils.RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.request.httpversion.join(".") + ].join(" "); + var content = null; + if (flow.request.contentLength > 0) { + content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); } else { - time = "..."; - } - return React.createElement("td", {className: "col-time"}, time); - } -}); - - -var all_columns = [ - TLSColumn, - IconColumn, - PathColumn, - MethodColumn, - StatusColumn, - SizeColumn, - TimeColumn -]; - -module.exports = all_columns; - -},{"../flow/utils.js":17,"../utils.js":20,"react":"react"}],8:[function(require,module,exports){ -var React = require("react"); -var common = require("./common.js"); -var utils = require("../utils.js"); -var _ = require("lodash"); + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + } -var VirtualScrollMixin = require("./virtualscroll.js"); -var flowtable_columns = require("./flowtable-columns.js"); + //TODO: Styling -var FlowRow = React.createClass({displayName: "FlowRow", + return ( + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.request}), + React.createElement("hr", null), + content + ) + ); + } +}); + +var Response = React.createClass({displayName: "Response", render: function () { var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return React.createElement(Column, {key: Column.displayName, flow: flow}); - }.bind(this)); - var className = ""; - if (this.props.selected) { - className += " selected"; - } - if (this.props.highlighted) { - className += " highlighted"; - } - if (flow.intercepted) { - className += " intercepted"; - } - if (flow.request) { - className += " has-request"; - } - if (flow.response) { - className += " has-response"; + var first_line = [ + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); + var content = null; + if (flow.response.contentLength > 0) { + content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); + } else { + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); } + //TODO: Styling + return ( - React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, - columns - )); - }, - shouldComponentUpdate: function (nextProps) { - return true; - // Further optimization could be done here - // by calling forceUpdate on flow updates, selection changes and column changes. - //return ( - //(this.props.columns.length !== nextProps.columns.length) || - //(this.props.selected !== nextProps.selected) - //); + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.response}), + React.createElement("hr", null), + content + ) + ); } }); -var FlowTableHead = React.createClass({displayName: "FlowTableHead", - getInitialState: function(){ - return { - sortColumn: undefined, - sortDesc: false - }; - }, - onClick: function(Column){ - var sortDesc = this.state.sortDesc; - var hasSort = Column.sortKeyFun; - if(Column === this.state.sortColumn){ - sortDesc = !sortDesc; - this.setState({ - sortDesc: sortDesc - }); - } else { - this.setState({ - sortColumn: hasSort && Column, - sortDesc: false - }) - } - var sortKeyFun; - if(!sortDesc){ - sortKeyFun = Column.sortKeyFun; - } else { - sortKeyFun = hasSort && function(){ - var k = Column.sortKeyFun.apply(this, arguments); - if(_.isString(k)){ - return utils.reverseString(""+k); - } else { - return -k; - } - } - } - this.props.setSortKeyFun(sortKeyFun); - }, +var Error = React.createClass({displayName: "Error", render: function () { - var columns = this.props.columns.map(function (Column) { - var onClick = this.onClick.bind(this, Column); - var className; - if(this.state.sortColumn === Column) { - if(this.state.sortDesc){ - className = "sort-desc"; - } else { - className = "sort-asc"; - } - } - return React.createElement(Column.Title, { - key: Column.displayName, - onClick: onClick, - className: className}); - }.bind(this)); - return React.createElement("thead", null, - React.createElement("tr", null, columns) + var flow = this.props.flow; + return ( + React.createElement("section", null, + React.createElement("div", {className: "alert alert-warning"}, + flow.error.msg, + React.createElement("div", null, + React.createElement("small", null, utils.formatTimeStamp(flow.error.timestamp) ) + ) + ) + ) ); } }); +module.exports = { + Request: Request, + Response: Response, + Error: Error +}; + +},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +var React = require("react"); -var ROW_HEIGHT = 32; +var actions = require("../../actions.js"); -var FlowTable = React.createClass({displayName: "FlowTable", - mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - _listen: function(view){ - if(!view){ - return; - } - view.addListener("add", this.onChange); - view.addListener("update", this.onChange); - view.addListener("remove", this.onChange); - view.addListener("recalculate", this.onChange); - }, - componentWillMount: function () { - this._listen(this.props.view); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add"); - this.props.view.removeListener("update"); - this.props.view.removeListener("remove"); - this.props.view.removeListener("recalculate"); - } - this._listen(nextProps.view); - } - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); +var NavAction = React.createClass({displayName: "NavAction", + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.props.view.index(flow), - this.refs.body.getDOMNode().offsetTop + render: function () { + return ( + React.createElement("a", {title: this.props.title, + href: "#", + className: "nav-action", + onClick: this.onClick}, + React.createElement("i", {className: "fa fa-fw " + this.props.icon}) + ) ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.props.view._highlight && - this.props.view._highlight[flow.id] - ); + } +}); - return React.createElement(FlowRow, {key: flow.id, - ref: flow.id, - flow: flow, - columns: this.state.columns, - selected: selected, - highlighted: highlighted, - selectFlow: this.props.selectFlow} - ); - }, +var Nav = React.createClass({displayName: "Nav", render: function () { - //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.list : []; + var flow = this.props.flow; - var rows = this.renderRows(flows); + var tabs = this.props.tabs.map(function (e) { + var str = e.charAt(0).toUpperCase() + e.slice(1); + var className = this.props.active === e ? "active" : ""; + var onClick = function (event) { + this.props.selectTab(e); + event.preventDefault(); + }.bind(this); + return React.createElement("a", {key: e, + href: "#", + className: className, + onClick: onClick}, str); + }.bind(this)); + + var acceptButton = null; + if(flow.intercepted){ + acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + } + var revertButton = null; + if(flow.modified){ + revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + } return ( - React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, - React.createElement("table", null, - React.createElement(FlowTableHead, {ref: "head", - columns: this.state.columns, - setSortKeyFun: this.props.setSortKeyFun}), - React.createElement("tbody", {ref: "body"}, - this.getPlaceholderTop(flows.length), - rows, - this.getPlaceholderBottom(flows.length) - ) - ) + React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, + tabs, + React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), + React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), + React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), + acceptButton, + revertButton ) ); } }); -module.exports = FlowTable; - +module.exports = Nav; -},{"../utils.js":20,"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1600,7 +1625,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],10:[function(require,module,exports){ +},{"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -1992,7 +2017,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":16,"../utils.js":20,"./common.js":4,"jquery":"jquery","react":"react"}],11:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2002,7 +2027,7 @@ var toputils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); FlowTable = require("./flowtable.js"); -var flowdetail = require("./flowdetail.js"); +var FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", mixins: [common.Navigation, common.State], @@ -2216,7 +2241,7 @@ var MainView = React.createClass({displayName: "MainView", if (selected) { details = [ React.createElement(common.Splitter, {key: "splitter"}), - React.createElement(flowdetail.FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected}) + React.createElement(FlowView, {key: "flowDetails", ref: "flowDetails", flow: selected}) ]; } else { details = null; @@ -2238,7 +2263,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":16,"../store/view.js":19,"../utils.js":20,"./common.js":4,"./flowdetail.js":6,"./flowtable.js":8,"react":"react"}],12:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2333,7 +2358,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":18,"./common.js":4,"./eventlog.js":5,"./footer.js":9,"./header.js":10,"./mainview.js":11,"lodash":"lodash","react":"react","react-router":"react-router"}],13:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2420,7 +2445,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],14:[function(require,module,exports){ +},{"react":"react"}],17:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2450,7 +2475,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],15:[function(require,module,exports){ +},{"./actions.js":2}],18:[function(require,module,exports){ var flux = require("flux"); @@ -2474,7 +2499,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],16:[function(require,module,exports){ +},{"flux":"flux"}],19:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4250,10 +4275,10 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":17}],17:[function(require,module,exports){ +},{"../flow/utils.js":20}],20:[function(require,module,exports){ var _ = require("lodash"); -var _MessageUtils = { +var MessageUtils = { getContentType: function (message) { return this.get_first_header(message, /^Content-Type$/i); }, @@ -4295,7 +4320,7 @@ var defaultPorts = { "https": 443 }; -var RequestUtils = _.extend(_MessageUtils, { +var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; @@ -4309,16 +4334,16 @@ var RequestUtils = _.extend(_MessageUtils, { } }); -var ResponseUtils = _.extend(_MessageUtils, {}); +var ResponseUtils = _.extend(MessageUtils, {}); module.exports = { ResponseUtils: ResponseUtils, - RequestUtils: RequestUtils - -} + RequestUtils: RequestUtils, + MessageUtils: MessageUtils +}; -},{"lodash":"lodash"}],18:[function(require,module,exports){ +},{"lodash":"lodash"}],21:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4501,7 +4526,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":15,"../utils.js":20,"events":1,"jquery":"jquery","lodash":"lodash"}],19:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4617,7 +4642,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":20,"events":1,"lodash":"lodash"}],20:[function(require,module,exports){ +},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); diff --git a/web/src/js/components/flowdetail.js b/web/src/js/components/flowdetail.js deleted file mode 100644 index 1d019ffb..00000000 --- a/web/src/js/components/flowdetail.js +++ /dev/null @@ -1,399 +0,0 @@ -var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); - -var NavAction = React.createClass({ - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); - }, - render: function () { - return ( - - - - ); - } -}); - -var FlowDetailNav = React.createClass({ - render: function () { - var flow = this.props.flow; - - var tabs = this.props.tabs.map(function (e) { - var str = e.charAt(0).toUpperCase() + e.slice(1); - var className = this.props.active === e ? "active" : ""; - var onClick = function (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return {str}; - }.bind(this)); - - var acceptButton = null; - if(flow.intercepted){ - acceptButton = ; - } - var revertButton = null; - if(flow.modified){ - revertButton = ; - } - - return ( - - ); - } -}); - -var Headers = React.createClass({ - render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - - {header[0] + ":"} - {header[1]} - - ); - }); - return ( - - - {rows} - -
- ); - } -}); - -var FlowDetailRequest = React.createClass({ - render: function () { - var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); - } else { - content =
No Content
; - } - - //TODO: Styling - - return ( -
-
{ first_line }
- -
- {content} -
- ); - } -}); - -var FlowDetailResponse = React.createClass({ - render: function () { - var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); - } else { - content =
No Content
; - } - - //TODO: Styling - - return ( -
-
{ first_line }
- -
- {content} -
- ); - } -}); - -var FlowDetailError = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( -
-
- {flow.error.msg} -
- { toputils.formatTimeStamp(flow.error.timestamp) } -
-
-
- ); - } -}); - -var TimeStamp = React.createClass({ - render: function () { - - if (!this.props.t) { - //should be return null, but that triggers a React bug. - return ; - } - - var ts = toputils.formatTimeStamp(this.props.t); - - var delta; - if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); - delta = {"(" + delta + ")"}; - } else { - delta = null; - } - - return - {this.props.title + ":"} - {ts} {delta} - ; - } -}); - -var ConnectionInfo = React.createClass({ - - render: function () { - var conn = this.props.conn; - var address = conn.address.address.join(":"); - - var sni = ; //should be null, but that triggers a React bug. - if (conn.sni) { - sni = - - TLS SNI: - - {conn.sni} - ; - } - return ( - - - - - - - {sni} - -
Address:{address}
- ); - } -}); - -var CertificateInfo = React.createClass({ - render: function () { - //TODO: We should fetch human-readable certificate representation - // from the server - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - - var preStyle = {maxHeight: 100}; - return ( -
- {client_conn.cert ?

Client Certificate

: null} - {client_conn.cert ?
{client_conn.cert}
: null} - - {server_conn.cert ?

Server Certificate

: null} - {server_conn.cert ?
{server_conn.cert}
: null} -
- ); - } -}); - -var Timing = React.createClass({ - render: function () { - var flow = this.props.flow; - var sc = flow.server_conn; - var cc = flow.client_conn; - var req = flow.request; - var resp = flow.response; - - var timestamps = [ - { - title: "Server conn. initiated", - t: sc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Server conn. TCP handshake", - t: sc.timestamp_tcp_setup, - deltaTo: req.timestamp_start - }, { - title: "Server conn. SSL handshake", - t: sc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "Client conn. established", - t: cc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Client conn. SSL handshake", - t: cc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "First request byte", - t: req.timestamp_start, - }, { - title: "Request complete", - t: req.timestamp_end, - deltaTo: req.timestamp_start - } - ]; - - if (flow.response) { - timestamps.push( - { - title: "First response byte", - t: resp.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Response complete", - t: resp.timestamp_end, - deltaTo: req.timestamp_start - } - ); - } - - //Add unique key for each row. - timestamps.forEach(function (e) { - e.key = e.title; - }); - - timestamps = _.sortBy(timestamps, 't'); - - var rows = timestamps.map(function (e) { - return ; - }); - - return ( -
-

Timing

- - - {rows} - -
-
- ); - } -}); - -var FlowDetailConnectionInfo = React.createClass({ - render: function () { - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - return ( -
- -

Client Connection

- - -

Server Connection

- - - - - - -
- ); - } -}); - -var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo -}; - -var FlowDetail = React.createClass({ - mixins: [common.StickyHeadMixin, common.Navigation, common.State], - getTabs: function (flow) { - var tabs = []; - ["request", "response", "error"].forEach(function (e) { - if (flow[e]) { - tabs.push(e); - } - }); - tabs.push("details"); - return tabs; - }, - nextTab: function (i) { - var tabs = this.getTabs(this.props.flow); - var currentIndex = tabs.indexOf(this.getParams().detailTab); - // JS modulo operator doesn't correct negative numbers, make sure that we are positive. - var nextIndex = (currentIndex + i + tabs.length) % tabs.length; - this.selectTab(tabs[nextIndex]); - }, - selectTab: function (panel) { - this.replaceWith( - "flow", - { - flowId: this.getParams().flowId, - detailTab: panel - } - ); - }, - render: function () { - var flow = this.props.flow; - var tabs = this.getTabs(flow); - var active = this.getParams().detailTab; - - if (!_.contains(tabs, active)) { - if (active === "response" && flow.error) { - active = "error"; - } else if (active === "error" && flow.response) { - active = "response"; - } else { - active = tabs[0]; - } - this.selectTab(active); - } - - var Tab = allTabs[active]; - return ( -
- - -
- ); - } -}); - -module.exports = { - FlowDetail: FlowDetail -}; \ No newline at end of file diff --git a/web/src/js/components/flowview/details.js b/web/src/js/components/flowview/details.js new file mode 100644 index 00000000..00e0116c --- /dev/null +++ b/web/src/js/components/flowview/details.js @@ -0,0 +1,181 @@ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + +var TimeStamp = React.createClass({ + render: function () { + + if (!this.props.t) { + //should be return null, but that triggers a React bug. + return ; + } + + var ts = utils.formatTimeStamp(this.props.t); + + var delta; + if (this.props.deltaTo) { + delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = {"(" + delta + ")"}; + } else { + delta = null; + } + + return + {this.props.title + ":"} + {ts} {delta} + ; + } +}); + +var ConnectionInfo = React.createClass({ + + render: function () { + var conn = this.props.conn; + var address = conn.address.address.join(":"); + + var sni = ; //should be null, but that triggers a React bug. + if (conn.sni) { + sni = + + TLS SNI: + + {conn.sni} + ; + } + return ( + + + + + + + {sni} + +
Address:{address}
+ ); + } +}); + +var CertificateInfo = React.createClass({ + render: function () { + //TODO: We should fetch human-readable certificate representation + // from the server + var flow = this.props.flow; + var client_conn = flow.client_conn; + var server_conn = flow.server_conn; + + var preStyle = {maxHeight: 100}; + return ( +
+ {client_conn.cert ?

Client Certificate

: null} + {client_conn.cert ?
{client_conn.cert}
: null} + + {server_conn.cert ?

Server Certificate

: null} + {server_conn.cert ?
{server_conn.cert}
: null} +
+ ); + } +}); + +var Timing = React.createClass({ + render: function () { + var flow = this.props.flow; + var sc = flow.server_conn; + var cc = flow.client_conn; + var req = flow.request; + var resp = flow.response; + + var timestamps = [ + { + title: "Server conn. initiated", + t: sc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Server conn. TCP handshake", + t: sc.timestamp_tcp_setup, + deltaTo: req.timestamp_start + }, { + title: "Server conn. SSL handshake", + t: sc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "Client conn. established", + t: cc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Client conn. SSL handshake", + t: cc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "First request byte", + t: req.timestamp_start, + }, { + title: "Request complete", + t: req.timestamp_end, + deltaTo: req.timestamp_start + } + ]; + + if (flow.response) { + timestamps.push( + { + title: "First response byte", + t: resp.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Response complete", + t: resp.timestamp_end, + deltaTo: req.timestamp_start + } + ); + } + + //Add unique key for each row. + timestamps.forEach(function (e) { + e.key = e.title; + }); + + timestamps = _.sortBy(timestamps, 't'); + + var rows = timestamps.map(function (e) { + return ; + }); + + return ( +
+

Timing

+ + + {rows} + +
+
+ ); + } +}); + +var Details = React.createClass({ + render: function () { + var flow = this.props.flow; + var client_conn = flow.client_conn; + var server_conn = flow.server_conn; + return ( +
+ +

Client Connection

+ + +

Server Connection

+ + + + + + +
+ ); + } +}); + +module.exports = Details; \ No newline at end of file diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js new file mode 100644 index 00000000..0c31aca5 --- /dev/null +++ b/web/src/js/components/flowview/index.js @@ -0,0 +1,74 @@ +var React = require("react"); +var _ = require("lodash"); + +var common = require("../common.js"); +var Nav = require("./nav.js"); +var Messages = require("./messages.js"); +var Details = require("./details.js"); + +var allTabs = { + request: Messages.Request, + response: Messages.Response, + error: Messages.Error, + details: Details +}; + +var FlowView = React.createClass({ + mixins: [common.StickyHeadMixin, common.Navigation, common.State], + getTabs: function (flow) { + var tabs = []; + ["request", "response", "error"].forEach(function (e) { + if (flow[e]) { + tabs.push(e); + } + }); + tabs.push("details"); + return tabs; + }, + nextTab: function (i) { + var tabs = this.getTabs(this.props.flow); + var currentIndex = tabs.indexOf(this.getParams().detailTab); + // JS modulo operator doesn't correct negative numbers, make sure that we are positive. + var nextIndex = (currentIndex + i + tabs.length) % tabs.length; + this.selectTab(tabs[nextIndex]); + }, + selectTab: function (panel) { + this.replaceWith( + "flow", + { + flowId: this.getParams().flowId, + detailTab: panel + } + ); + }, + render: function () { + var flow = this.props.flow; + var tabs = this.getTabs(flow); + var active = this.getParams().detailTab; + + if (!_.contains(tabs, active)) { + if (active === "response" && flow.error) { + active = "error"; + } else if (active === "error" && flow.response) { + active = "response"; + } else { + active = tabs[0]; + } + this.selectTab(active); + } + + var Tab = allTabs[active]; + return ( +
+
+ ); + } +}); + +module.exports = FlowView; \ No newline at end of file diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js new file mode 100644 index 00000000..ffbfff43 --- /dev/null +++ b/web/src/js/components/flowview/messages.js @@ -0,0 +1,102 @@ +var React = require("react"); + +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); + +var Headers = React.createClass({ + render: function () { + var rows = this.props.message.headers.map(function (header, i) { + return ( + + {header[0] + ":"} + {header[1]} + + ); + }); + return ( + + + {rows} + +
+ ); + } +}); + +var Request = React.createClass({ + render: function () { + var flow = this.props.flow; + var first_line = [ + flow.request.method, + flowutils.RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.request.httpversion.join(".") + ].join(" "); + var content = null; + if (flow.request.contentLength > 0) { + content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); + } else { + content =
No Content
; + } + + //TODO: Styling + + return ( +
+
{ first_line }
+ +
+ {content} +
+ ); + } +}); + +var Response = React.createClass({ + render: function () { + var flow = this.props.flow; + var first_line = [ + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); + var content = null; + if (flow.response.contentLength > 0) { + content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); + } else { + content =
No Content
; + } + + //TODO: Styling + + return ( +
+
{ first_line }
+ +
+ {content} +
+ ); + } +}); + +var Error = React.createClass({ + render: function () { + var flow = this.props.flow; + return ( +
+
+ {flow.error.msg} +
+ { utils.formatTimeStamp(flow.error.timestamp) } +
+
+
+ ); + } +}); + +module.exports = { + Request: Request, + Response: Response, + Error: Error +}; \ No newline at end of file diff --git a/web/src/js/components/flowview/nav.js b/web/src/js/components/flowview/nav.js new file mode 100644 index 00000000..46eda707 --- /dev/null +++ b/web/src/js/components/flowview/nav.js @@ -0,0 +1,61 @@ +var React = require("react"); + +var actions = require("../../actions.js"); + +var NavAction = React.createClass({ + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); + }, + render: function () { + return ( + + + + ); + } +}); + +var Nav = React.createClass({ + render: function () { + var flow = this.props.flow; + + var tabs = this.props.tabs.map(function (e) { + var str = e.charAt(0).toUpperCase() + e.slice(1); + var className = this.props.active === e ? "active" : ""; + var onClick = function (event) { + this.props.selectTab(e); + event.preventDefault(); + }.bind(this); + return {str}; + }.bind(this)); + + var acceptButton = null; + if(flow.intercepted){ + acceptButton = ; + } + var revertButton = null; + if(flow.modified){ + revertButton = ; + } + + return ( + + ); + } +}); + +module.exports = Nav; \ No newline at end of file diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 8eda2e9c..81bf3b03 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -7,7 +7,7 @@ var toputils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); FlowTable = require("./flowtable.js"); -var flowdetail = require("./flowdetail.js"); +var FlowView = require("./flowview/index.js"); var MainView = React.createClass({ mixins: [common.Navigation, common.State], @@ -221,7 +221,7 @@ var MainView = React.createClass({ if (selected) { details = [ , - + ]; } else { details = null; diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js index a95d4ffe..dd7f763b 100644 --- a/web/src/js/flow/utils.js +++ b/web/src/js/flow/utils.js @@ -1,6 +1,6 @@ var _ = require("lodash"); -var _MessageUtils = { +var MessageUtils = { getContentType: function (message) { return this.get_first_header(message, /^Content-Type$/i); }, @@ -42,7 +42,7 @@ var defaultPorts = { "https": 443 }; -var RequestUtils = _.extend(_MessageUtils, { +var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; @@ -56,11 +56,11 @@ var RequestUtils = _.extend(_MessageUtils, { } }); -var ResponseUtils = _.extend(_MessageUtils, {}); +var ResponseUtils = _.extend(MessageUtils, {}); module.exports = { ResponseUtils: ResponseUtils, - RequestUtils: RequestUtils - -} \ No newline at end of file + RequestUtils: RequestUtils, + MessageUtils: MessageUtils +}; \ No newline at end of file -- cgit v1.2.3 From 1143552e1690f8b96b3d95381f7f06cbb46ead59 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 00:21:38 +0100 Subject: web: add content views --- libmproxy/web/static/app.css | 10 ++ libmproxy/web/static/app.js | 215 ++++++++++++++++++++++---- web/src/css/app.less | 1 + web/src/css/flowdetail.less | 3 + web/src/css/flowview.less | 9 ++ web/src/js/components/flowview/contentview.js | 158 +++++++++++++++++++ web/src/js/components/flowview/messages.js | 17 +- 7 files changed, 366 insertions(+), 47 deletions(-) create mode 100644 web/src/css/flowview.less create mode 100644 web/src/js/components/flowview/contentview.js diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index 4f24ddd9..cf2db2c6 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -290,6 +290,9 @@ header .menu { max-height: 100px; overflow-y: auto; } +.view-selector { + margin-top: 10px; +} .flow-detail table { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; width: 100%; @@ -316,6 +319,13 @@ header .menu { text-overflow: ellipsis; white-space: nowrap; } +.flowview-image { + text-align: center; +} +.flowview-image img { + max-width: 100%; + max-height: 100%; +} .eventlog { height: 200px; flex: 0 0 auto; diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 04d6f282..eb8ef45e 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":16,"./connection":18,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":23,"./common.js":4,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var RequestUtils = require("../flow/utils.js").RequestUtils; var ResponseUtils = require("../flow/utils.js").ResponseUtils; @@ -980,7 +980,7 @@ var all_columns = [ module.exports = all_columns; -},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +},{"../flow/utils.js":21,"../utils.js":24,"react":"react"}],7:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); var utils = require("../utils.js"); @@ -1180,7 +1180,167 @@ var FlowTable = React.createClass({displayName: "FlowTable", module.exports = FlowTable; -},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +},{"../utils.js":24,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var Image = React.createClass({displayName: "Image", + statics: { + matches: function (message) { + return image_regex.test(MessageUtils.getContentType(message)); + } + }, + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + return React.createElement("div", {className: "flowview-image"}, + React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) + ); + } +}); + +var Raw = React.createClass({displayName: "Raw", + statics: { + matches: function (message) { + return true; + } + }, + render: function () { + //FIXME + return React.createElement("div", null, "raw"); + } +}); + + +var Auto = React.createClass({displayName: "Auto", + statics: { + matches: function () { + return false; // don't match itself + }, + findView: function (message) { + for (var i = 0; i < all.length; i++) { + if (all[i].matches(message)) { + return all[i]; + } + } + return all[all.length - 1]; + } + }, + render: function () { + var View = Auto.findView(this.props.message); + return React.createElement(View, React.__spread({}, this.props)); + } +}); + +var all = [Auto, Image, Raw]; + + +var ContentEmpty = React.createClass({displayName: "ContentEmpty", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + return React.createElement("div", {className: "alert alert-info"}, "No ", message_name, " content."); + } +}); + +var ContentMissing = React.createClass({displayName: "ContentMissing", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; + return React.createElement("div", {className: "alert alert-info"}, message_name, " content missing."); + } +}); + +var TooLarge = React.createClass({displayName: "TooLarge", + render: function () { + var size = utils.formatSize(this.props.message.contentLength); + return React.createElement("div", {className: "alert alert-warning"}, + React.createElement("button", {onClick: this.props.onClick, className: "btn btn-xs btn-warning pull-right"}, "Display anyway"), + size, " content size." + ); + } +}); + +var ViewSelector = React.createClass({displayName: "ViewSelector", + render: function () { + var views = []; + for (var i = 0; i < all.length; i++) { + var view = all[i]; + var className = "btn btn-default"; + if (view === this.props.active) { + className += " active"; + } + var text; + if (view === Auto) { + text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); + } else { + text = view.displayName.toLowerCase(); + } + views.push( + React.createElement("button", { + key: view.displayName, + onClick: this.props.selectView.bind(null, view), + className: className}, + text + ) + ); + } + + return React.createElement("div", {className: "view-selector btn-group btn-group-xs"}, views); + } +}); + +var ContentView = React.createClass({displayName: "ContentView", + getInitialState: function () { + return { + displayLarge: false, + View: Auto + }; + }, + propTypes: { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, + selectView: function (view) { + this.setState({ + View: view + }); + }, + displayLarge: function () { + this.setState({displayLarge: true}); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.setState(this.getInitialState()); + } + }, + render: function () { + var message = this.props.message; + if (message.contentLength === 0) { + return React.createElement(ContentEmpty, React.__spread({}, this.props)); + } else if (message.contentLength === null) { + return React.createElement(ContentMissing, React.__spread({}, this.props)); + } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { + return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); + } + + return React.createElement("div", null, + React.createElement(this.state.View, React.__spread({}, this.props)), + React.createElement("div", {className: "text-center"}, + React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}) + ) + ); + } +}); + +module.exports = ContentView; + +},{"../../flow/utils.js":21,"../../utils.js":24,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1363,7 +1523,7 @@ var Details = React.createClass({displayName: "Details", module.exports = Details; -},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../utils.js":24,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1439,11 +1599,12 @@ var FlowView = React.createClass({displayName: "FlowView", module.exports = FlowView; -},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ +},{"../common.js":4,"./details.js":9,"./messages.js":11,"./nav.js":12,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ var React = require("react"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); var Headers = React.createClass({displayName: "Headers", render: function () { @@ -1473,12 +1634,6 @@ var Request = React.createClass({displayName: "Request", flowutils.RequestUtils.pretty_url(flow.request), "HTTP/" + flow.request.httpversion.join(".") ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1487,7 +1642,7 @@ var Request = React.createClass({displayName: "Request", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.request}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.request}) ) ); } @@ -1501,12 +1656,6 @@ var Response = React.createClass({displayName: "Response", flow.response.code, flow.response.msg ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1515,7 +1664,7 @@ var Response = React.createClass({displayName: "Response", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.response}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.response}) ) ); } @@ -1543,7 +1692,7 @@ module.exports = { Error: Error }; -},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +},{"../../flow/utils.js":21,"../../utils.js":24,"./contentview.js":8,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var actions = require("../../actions.js"); @@ -1606,7 +1755,7 @@ var Nav = React.createClass({displayName: "Nav", module.exports = Nav; -},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1625,7 +1774,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],13:[function(require,module,exports){ +},{"react":"react"}],14:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -2017,7 +2166,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../utils.js":24,"./common.js":4,"jquery":"jquery","react":"react"}],15:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2263,7 +2412,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../store/view.js":23,"../utils.js":24,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":10,"react":"react"}],16:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2358,7 +2507,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":22,"./common.js":4,"./eventlog.js":5,"./footer.js":13,"./header.js":14,"./mainview.js":15,"lodash":"lodash","react":"react","react-router":"react-router"}],17:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2445,7 +2594,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],17:[function(require,module,exports){ +},{"react":"react"}],18:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2475,7 +2624,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],18:[function(require,module,exports){ +},{"./actions.js":2}],19:[function(require,module,exports){ var flux = require("flux"); @@ -2499,7 +2648,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],19:[function(require,module,exports){ +},{"flux":"flux"}],20:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4275,7 +4424,7 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":20}],20:[function(require,module,exports){ +},{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); var MessageUtils = { @@ -4343,7 +4492,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],21:[function(require,module,exports){ +},{"lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4526,7 +4675,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":19,"../utils.js":24,"events":1,"jquery":"jquery","lodash":"lodash"}],23:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4642,7 +4791,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ +},{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); diff --git a/web/src/css/app.less b/web/src/css/app.less index 26f22572..ecec3d9c 100644 --- a/web/src/css/app.less +++ b/web/src/css/app.less @@ -13,5 +13,6 @@ html { @import (less) "header.less"; @import (less) "flowtable.less"; @import (less) "flowdetail.less"; +@import (less) "flowview.less"; @import (less) "eventlog.less"; @import (less) "footer.less"; \ No newline at end of file diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less index 7649057f..093ee14f 100644 --- a/web/src/css/flowdetail.less +++ b/web/src/css/flowdetail.less @@ -29,6 +29,9 @@ } } +.view-selector { + margin-top: 10px; +} .flow-detail table { .monospace(); diff --git a/web/src/css/flowview.less b/web/src/css/flowview.less new file mode 100644 index 00000000..44ae8ac2 --- /dev/null +++ b/web/src/css/flowview.less @@ -0,0 +1,9 @@ +.flowview-image { + + text-align: center; + + img { + max-width: 100%; + max-height: 100%; + } +} \ No newline at end of file diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js new file mode 100644 index 00000000..09a64bb2 --- /dev/null +++ b/web/src/js/components/flowview/contentview.js @@ -0,0 +1,158 @@ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var Image = React.createClass({ + statics: { + matches: function (message) { + return image_regex.test(MessageUtils.getContentType(message)); + } + }, + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + return
+ preview +
; + } +}); + +var Raw = React.createClass({ + statics: { + matches: function (message) { + return true; + } + }, + render: function () { + //FIXME + return
raw
; + } +}); + + +var Auto = React.createClass({ + statics: { + matches: function () { + return false; // don't match itself + }, + findView: function (message) { + for (var i = 0; i < all.length; i++) { + if (all[i].matches(message)) { + return all[i]; + } + } + return all[all.length - 1]; + } + }, + render: function () { + var View = Auto.findView(this.props.message); + return ; + } +}); + +var all = [Auto, Image, Raw]; + + +var ContentEmpty = React.createClass({ + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + return
No {message_name} content.
; + } +}); + +var ContentMissing = React.createClass({ + render: function () { + var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; + return
{message_name} content missing.
; + } +}); + +var TooLarge = React.createClass({ + render: function () { + var size = utils.formatSize(this.props.message.contentLength); + return
+ + {size} content size. +
; + } +}); + +var ViewSelector = React.createClass({ + render: function () { + var views = []; + for (var i = 0; i < all.length; i++) { + var view = all[i]; + var className = "btn btn-default"; + if (view === this.props.active) { + className += " active"; + } + var text; + if (view === Auto) { + text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); + } else { + text = view.displayName.toLowerCase(); + } + views.push( + + ); + } + + return
{views}
; + } +}); + +var ContentView = React.createClass({ + getInitialState: function () { + return { + displayLarge: false, + View: Auto + }; + }, + propTypes: { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, + selectView: function (view) { + this.setState({ + View: view + }); + }, + displayLarge: function () { + this.setState({displayLarge: true}); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.setState(this.getInitialState()); + } + }, + render: function () { + var message = this.props.message; + if (message.contentLength === 0) { + return ; + } else if (message.contentLength === null) { + return ; + } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { + return ; + } + + return
+ +
+ +
+
; + } +}); + +module.exports = ContentView; \ No newline at end of file diff --git a/web/src/js/components/flowview/messages.js b/web/src/js/components/flowview/messages.js index ffbfff43..fe8fa200 100644 --- a/web/src/js/components/flowview/messages.js +++ b/web/src/js/components/flowview/messages.js @@ -2,6 +2,7 @@ var React = require("react"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); var Headers = React.createClass({ render: function () { @@ -31,12 +32,6 @@ var Request = React.createClass({ flowutils.RequestUtils.pretty_url(flow.request), "HTTP/" + flow.request.httpversion.join(".") ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); - } else { - content =
No Content
; - } //TODO: Styling @@ -45,7 +40,7 @@ var Request = React.createClass({
{ first_line }

- {content} + ); } @@ -59,12 +54,6 @@ var Response = React.createClass({ flow.response.code, flow.response.msg ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); - } else { - content =
No Content
; - } //TODO: Styling @@ -73,7 +62,7 @@ var Response = React.createClass({
{ first_line }

- {content} + ); } -- cgit v1.2.3 From 89d66360d6f7caa9760fe56fa146396b1b4251dc Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 00:28:08 +0100 Subject: tweak css --- libmproxy/web/static/app.css | 6 + web/src/css/eventlog.less | 2 - web/src/css/flowdetail.less | 23 +-- web/src/css/flowtable.less | 208 ++++++++++++++-------------- web/src/css/flowview.less | 10 +- web/src/css/header.less | 32 ++--- web/src/css/layout.less | 2 +- web/src/css/sprites.less | 28 ++-- web/src/css/vendor-bootstrap-variables.less | 11 +- web/src/css/vendor-bootstrap.less | 5 - 10 files changed, 170 insertions(+), 157 deletions(-) diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index cf2db2c6..91e847a4 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -290,6 +290,9 @@ header .menu { max-height: 100px; overflow-y: auto; } +.flow-detail hr { + margin: 0 0 5px; +} .view-selector { margin-top: 10px; } @@ -309,6 +312,9 @@ header .menu { width: 50%; padding-right: 1em; } +.header-table td { + line-height: 1.3em; +} .header-table .header-name { width: 33%; padding-right: 1em; diff --git a/web/src/css/eventlog.less b/web/src/css/eventlog.less index 8b0a7647..26dea3cc 100644 --- a/web/src/css/eventlog.less +++ b/web/src/css/eventlog.less @@ -6,7 +6,6 @@ display: flex; flex-direction: column; - > div { background-color: #F2F2F2; padding: 0 5px; @@ -23,7 +22,6 @@ background-color: #fcfcfc; } - .fa-close { cursor: pointer; float: right; diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less index 093ee14f..453cf425 100644 --- a/web/src/css/flowdetail.less +++ b/web/src/css/flowdetail.less @@ -1,13 +1,12 @@ //TODO: Move into some utils -.monospace(){ +.monospace() { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } - .flow-detail { width: 100%; overflow: auto; - + nav { background-color: #F2F2F2; } @@ -27,10 +26,15 @@ max-height: 100px; overflow-y: auto; } + + hr { + margin: 0 0 5px; + } + } .view-selector { - margin-top: 10px; + margin-top: 10px; } .flow-detail table { @@ -38,10 +42,10 @@ width: 100%; table-layout: fixed; word-break: break-all; - + tr { - &:not(:first-child){ - border-top: 1px solid #f7f7f7; + &:not(:first-child) { + border-top: 1px solid #f7f7f7; } } @@ -62,12 +66,15 @@ } .header-table { + td { + line-height: 1.3em; + } .header-name { width: 33%; padding-right: 1em; } .header-value { - + } } diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less index d94d9370..3533983c 100644 --- a/web/src/css/flowtable.less +++ b/web/src/css/flowtable.less @@ -1,127 +1,127 @@ //TODO: move into utils .user-select (@val) { - -webkit-touch-callout: @val; - -webkit-user-select: @val; - -khtml-user-select: @val; - -moz-user-select: @val; - -ms-user-select: @val; - user-select: @val; + -webkit-touch-callout: @val; + -webkit-user-select: @val; + -khtml-user-select: @val; + -moz-user-select: @val; + -ms-user-select: @val; + user-select: @val; } .flow-table { - width: 100%; - overflow: auto; - - table { width: 100%; - table-layout: fixed; - } - - thead { - background-color: #F2F2F2; - line-height: 23px; - } - - th { - font-weight: normal; - box-shadow: 0 1px 0 #a6a6a6; - position: relative !important; - padding-left: 1px; - .user-select(none); + overflow: auto; - &.sort-asc, &.sort-desc { - background-color: lighten(#F2F2F2, 3%); - } - &.sort-asc:after, &.sort-desc:after { - font: normal normal normal 14px/1 FontAwesome; - position: absolute; - right: 3px; - top: 3px; - padding: 2px; - background-color: fadeout(lighten(#F2F2F2, 3%), 20%); - } - &.sort-asc:after { - content: "\f0de"; + table { + width: 100%; + table-layout: fixed; } - &.sort-desc:after { - content: "\f0dd"; + + thead { + background-color: #F2F2F2; + line-height: 23px; } - } + th { + font-weight: normal; + box-shadow: 0 1px 0 #a6a6a6; + position: relative !important; + padding-left: 1px; + .user-select(none); - tr { - cursor: pointer; + &.sort-asc, &.sort-desc { + background-color: lighten(#F2F2F2, 3%); + } + &.sort-asc:after, &.sort-desc:after { + font: normal normal normal 14px/1 FontAwesome; + position: absolute; + right: 3px; + top: 3px; + padding: 2px; + background-color: fadeout(lighten(#F2F2F2, 3%), 20%); + } + &.sort-asc:after { + content: "\f0de"; + } + &.sort-desc:after { + content: "\f0dd"; + } - &:nth-child(even) { - background-color: rgba(0, 0, 0, 0.05); - } - &.selected { - background-color: hsla(209, 52%, 84%, 0.5) !important; - } - &.highlighted { - background-color: hsla(48, 100%, 50%, 0.4); } - &.highlighted:nth-child(even) { - background-color: hsla(48, 100%, 50%, 0.5); + + tr { + cursor: pointer; + + &:nth-child(even) { + background-color: rgba(0, 0, 0, 0.05); + } + &.selected { + background-color: hsla(209, 52%, 84%, 0.5) !important; + } + &.highlighted { + background-color: hsla(48, 100%, 50%, 0.4); + } + &.highlighted:nth-child(even) { + background-color: hsla(48, 100%, 50%, 0.5); + } } - } - td { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } + td { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } - @interceptorange: hsl(30, 100%, 50%); + @interceptorange: hsl(30, 100%, 50%); - tr.intercepted:not(.has-response) { - .col-path, .col-method { - color: @interceptorange; + tr.intercepted:not(.has-response) { + .col-path, .col-method { + color: @interceptorange; + } } - } - tr.intercepted.has-response { - .col-status, .col-size, .col-time { - color: @interceptorange; + tr.intercepted.has-response { + .col-status, .col-size, .col-time { + color: @interceptorange; + } } - } - .fa { - line-height: inherit; - &.pull-right { - margin-left: 0; + .fa { + line-height: inherit; + &.pull-right { + margin-left: 0; + } } - } - .col-tls { - width: 10px; - } - .col-tls-https { - background-color: rgba(0, 185, 0, 0.5); - } - .col-icon { - width: 32px; - } - .col-path { - .fa-repeat { - color: green; - } - .fa-pause { - color: @interceptorange; - } - } - .col-method { - width: 60px; - } - .col-status { - width: 50px; - } - .col-size { - width: 70px; - } - .col-time { - width: 50px; - } - td.col-time, td.col-size { - text-align: right; - } + .col-tls { + width: 10px; + } + .col-tls-https { + background-color: rgba(0, 185, 0, 0.5); + } + .col-icon { + width: 32px; + } + .col-path { + .fa-repeat { + color: green; + } + .fa-pause { + color: @interceptorange; + } + } + .col-method { + width: 60px; + } + .col-status { + width: 50px; + } + .col-size { + width: 70px; + } + .col-time { + width: 50px; + } + td.col-time, td.col-size { + text-align: right; + } } \ No newline at end of file diff --git a/web/src/css/flowview.less b/web/src/css/flowview.less index 44ae8ac2..aa8a2df2 100644 --- a/web/src/css/flowview.less +++ b/web/src/css/flowview.less @@ -1,9 +1,9 @@ .flowview-image { - text-align: center; + text-align: center; - img { - max-width: 100%; - max-height: 100%; - } + img { + max-width: 100%; + max-height: 100%; + } } \ No newline at end of file diff --git a/web/src/css/header.less b/web/src/css/header.less index 57f122e8..6e61b956 100644 --- a/web/src/css/header.less +++ b/web/src/css/header.less @@ -2,30 +2,30 @@ @import (reference) '../../node_modules/bootstrap/less/mixins/grid.less'; header { - padding-top: 0.5em; - background-color: white; - @separator-color: lighten(grey, 15%); - .menu { - padding: 10px; - border-bottom: solid @separator-color 1px; - } + padding-top: 0.5em; + background-color: white; + @separator-color: lighten(grey, 15%); + .menu { + padding: 10px; + border-bottom: solid @separator-color 1px; + } } @menu-row-gutter-width: 5px; .menu-row { - .make-row(@menu-row-gutter-width); + .make-row(@menu-row-gutter-width); } .filter-input { - .make-md-column(3, @menu-row-gutter-width); + .make-md-column(3, @menu-row-gutter-width); } .filter-input .popover { - top: 27px; - display: block; - max-width: none; - .popover-content { - max-height: 500px; - overflow-y: auto; - } + top: 27px; + display: block; + max-width: none; + .popover-content { + max-height: 500px; + overflow-y: auto; + } } \ No newline at end of file diff --git a/web/src/css/layout.less b/web/src/css/layout.less index f6807f24..4e96609b 100644 --- a/web/src/css/layout.less +++ b/web/src/css/layout.less @@ -15,7 +15,7 @@ html, body, #container { .main-view { flex: 1 1 auto; - + display: flex; flex-direction: row; diff --git a/web/src/css/sprites.less b/web/src/css/sprites.less index 49b3600c..74131c5e 100644 --- a/web/src/css/sprites.less +++ b/web/src/css/sprites.less @@ -5,34 +5,42 @@ // From Chrome Dev Tools .resource-icon-css { - background-image: url(images/chrome-devtools/resourceCSSIcon.png); + background-image: url(images/chrome-devtools/resourceCSSIcon.png); } + .resource-icon-document { - background-image: url(images/chrome-devtools/resourceDocumentIcon.png); + background-image: url(images/chrome-devtools/resourceDocumentIcon.png); } + .resource-icon-js { - background-image: url(images/chrome-devtools/resourceJSIcon.png); + background-image: url(images/chrome-devtools/resourceJSIcon.png); } + .resource-icon-plain { - background-image: url(images/chrome-devtools/resourcePlainIcon.png); + background-image: url(images/chrome-devtools/resourcePlainIcon.png); } // Own .resource-icon-executable { - background-image: url(images/resourceExecutableIcon.png); + background-image: url(images/resourceExecutableIcon.png); } + .resource-icon-flash { - background-image: url(images/resourceFlashIcon.png); + background-image: url(images/resourceFlashIcon.png); } + .resource-icon-image { - background-image: url(images/resourceImageIcon.png); + background-image: url(images/resourceImageIcon.png); } + .resource-icon-java { - background-image: url(images/resourceJavaIcon.png); + background-image: url(images/resourceJavaIcon.png); } + .resource-icon-not-modified { - background-image: url(images/resourceNotModifiedIcon.png); + background-image: url(images/resourceNotModifiedIcon.png); } + .resource-icon-redirect { - background-image: url(images/resourceRedirectIcon.png); + background-image: url(images/resourceRedirectIcon.png); } \ No newline at end of file diff --git a/web/src/css/vendor-bootstrap-variables.less b/web/src/css/vendor-bootstrap-variables.less index b2818993..e2c37bf5 100644 --- a/web/src/css/vendor-bootstrap-variables.less +++ b/web/src/css/vendor-bootstrap-variables.less @@ -1,6 +1,5 @@ - -@navbar-height: 32px; -@navbar-default-link-color: #303030; -@navbar-default-color: #303030; -@navbar-default-bg: #ffffff; -@navbar-default-border: #e0e0e0; +@navbar-height: 32px; +@navbar-default-link-color: #303030; +@navbar-default-color: #303030; +@navbar-default-bg: #ffffff; +@navbar-default-border: #e0e0e0; diff --git a/web/src/css/vendor-bootstrap.less b/web/src/css/vendor-bootstrap.less index 0b3252fe..35fda379 100644 --- a/web/src/css/vendor-bootstrap.less +++ b/web/src/css/vendor-bootstrap.less @@ -2,12 +2,10 @@ @import "../../node_modules/bootstrap/less/variables.less"; @import "vendor-bootstrap-variables.less"; @import "../../node_modules/bootstrap/less/mixins.less"; - // Reset and dependencies @import "../../node_modules/bootstrap/less/normalize.less"; @import "../../node_modules/bootstrap/less/print.less"; @import "../../node_modules/bootstrap/less/glyphicons.less"; - // Core CSS @import "../../node_modules/bootstrap/less/scaffolding.less"; @import "../../node_modules/bootstrap/less/type.less"; @@ -16,7 +14,6 @@ @import "../../node_modules/bootstrap/less/tables.less"; @import "../../node_modules/bootstrap/less/forms.less"; @import "../../node_modules/bootstrap/less/buttons.less"; - // Components @import "../../node_modules/bootstrap/less/component-animations.less"; @import "../../node_modules/bootstrap/less/dropdowns.less"; @@ -39,13 +36,11 @@ @import "../../node_modules/bootstrap/less/responsive-embed.less"; @import "../../node_modules/bootstrap/less/wells.less"; @import "../../node_modules/bootstrap/less/close.less"; - // Components w/ JavaScript @import "../../node_modules/bootstrap/less/modals.less"; @import "../../node_modules/bootstrap/less/tooltip.less"; @import "../../node_modules/bootstrap/less/popovers.less"; @import "../../node_modules/bootstrap/less/carousel.less"; - // Utility classes @import "../../node_modules/bootstrap/less/utilities.less"; @import "../../node_modules/bootstrap/less/responsive-utilities.less"; -- cgit v1.2.3 From 89383e9c138f68caf1cc394174250c133d21aa04 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 13:32:24 +1300 Subject: Refactor status bar prompting to use signal system --- libmproxy/console/__init__.py | 58 +-------- libmproxy/console/common.py | 46 +++----- libmproxy/console/flowlist.py | 112 ++++++++++-------- libmproxy/console/flowview.py | 109 ++++++++++------- libmproxy/console/grideditor.py | 31 +++-- libmproxy/console/signals.py | 16 ++- libmproxy/console/statusbar.py | 84 +++++++++++-- libmproxy/console/window.py | 255 +++++++++++++++++++--------------------- test/tools/testpatt | 10 +- 9 files changed, 386 insertions(+), 335 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index aae7a9c4..d8eb8a41 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -272,7 +272,7 @@ class ConsoleMaster(flow.FlowMaster): self.eventlog = not self.eventlog self.view_flowlist() - def _readflow(self, paths): + def _readflows(self, path): """ Utitility function that reads a list of flows or prints an error to the UI if that fails. @@ -281,7 +281,7 @@ class ConsoleMaster(flow.FlowMaster): - a list of flows, otherwise. """ try: - return flow.read_flows_from_paths(paths) + return flow.read_flows_from_paths([path]) except flow.FlowReadError as e: if not self.statusbar: print >> sys.stderr, e.strerror @@ -291,12 +291,12 @@ class ConsoleMaster(flow.FlowMaster): return None def client_playback_path(self, path): - flows = self._readflow(path) + flows = self._readflows(path) if flows: self.start_client_playback(flows, False) def server_playback_path(self, path): - flows = self._readflow(path) + flows = self._readflows(path) if flows: self.start_server_playback( flows, @@ -387,7 +387,6 @@ class ConsoleMaster(flow.FlowMaster): self.header = None self.body = None self.help_context = None - self.prompting = False self.onekey = False self.loop = urwid.MainLoop( self.view, @@ -538,55 +537,6 @@ class ConsoleMaster(flow.FlowMaster): self.sync_list_view() return reterr - def path_prompt(self, prompt, text, callback, *args): - self.statusbar.path_prompt(prompt, text) - self.view.set_focus("footer") - self.prompting = (callback, args) - - def prompt(self, prompt, text, callback, *args): - self.statusbar.prompt(prompt, text) - self.view.set_focus("footer") - self.prompting = (callback, args) - - def prompt_edit(self, prompt, text, callback): - self.statusbar.prompt(prompt + ": ", text) - self.view.set_focus("footer") - self.prompting = (callback, []) - - def prompt_onekey(self, prompt, keys, callback, *args): - """ - Keys are a set of (word, key) tuples. The appropriate key in the - word is highlighted. - """ - prompt = [prompt, " ("] - mkup = [] - for i, e in enumerate(keys): - mkup.extend(common.highlight_key(e[0], e[1])) - if i < len(keys)-1: - mkup.append(",") - prompt.extend(mkup) - prompt.append(")? ") - self.onekey = set(i[1] for i in keys) - self.prompt(prompt, "", callback, *args) - - def prompt_done(self): - self.prompting = False - self.onekey = False - self.view.set_focus("body") - signals.status_message.send(message="") - - def prompt_execute(self, txt=None): - if not txt: - txt = self.statusbar.get_edit_text() - p, args = self.prompting - self.prompt_done() - msg = p(txt, *args) - if msg: - signals.status_message.send(message=msg, expire=1) - - def prompt_cancel(self): - self.prompt_done() - def accept_all(self): self.state.accept_all(self) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 9731b682..185480db 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -203,13 +203,11 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): - master.path_prompt( - prompt, - state.last_saveload, - save_data, - data, - master, - state + signals.status_path_prompt.send( + prompt = prompt, + text = state.last_saveload, + callback = save_data, + args = (data, master, state) ) @@ -263,14 +261,13 @@ def copy_flow(part, scope, flow, master, state): def save(k): if k == "y": ask_save_path("Save data: ", data, master, state) - - master.prompt_onekey( - "Cannot copy binary data to clipboard. Save as file?", - ( + signals.status_prompt_onekey.send( + prompt = "Cannot copy binary data to clipboard. Save as file?", + keys = ( ("yes", "y"), ("no", "n"), ), - save + callback = save ) @@ -282,14 +279,11 @@ def ask_copy_part(scope, flow, master, state): if scope != "s": choices.append(("url", "u")) - master.prompt_onekey( - "Copy", - choices, - copy_flow, - scope, - flow, - master, - state + signals.status_prompt_onekey.send( + prompt = "Copy", + keys = choices, + callback = copy_flow, + args = (scope, flow, master, state) ) @@ -306,16 +300,14 @@ def ask_save_body(part, master, state, flow): # We first need to determine whether we want to save the request or the # response content. if request_has_content and response_has_content: - master.prompt_onekey( - "Save", - ( + signals.status_prompt_onekey.send( + prompt = "Save", + keys = ( ("request", "q"), ("response", "s"), ), - ask_save_body, - master, - state, - flow + callback = ask_save_body, + args = (master, state, flow) ) elif response_has_content: ask_save_body("s", master, state, flow) diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index c8ecf15c..d4dd89d8 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -111,17 +111,17 @@ class ConnectionItem(urwid.WidgetWrap): def save_flows_prompt(self, k): if k == "a": - self.master.path_prompt( - "Save all flows to: ", - self.state.last_saveload, - self.master.save_flows + signals.status_path_prompt.send( + prompt = "Save all flows to: ", + text = self.state.last_saveload, + callback = self.master.save_flows ) else: - self.master.path_prompt( - "Save this flow to: ", - self.state.last_saveload, - self.master.save_one_flow, - self.flow + signals.status_path_prompt.send( + prompt = "Save this flow to: ", + text = self.state.last_saveload, + callback = self.master.save_one_flow, + args = (self.flow,) ) def stop_server_playback_prompt(self, a): @@ -150,10 +150,10 @@ class ConnectionItem(urwid.WidgetWrap): self.master.options.replay_ignore_host ) else: - self.master.path_prompt( - "Server replay path: ", - self.state.last_saveload, - self.master.server_playback_path + signals.status_path_prompt.send( + prompt = "Server replay path: ", + text = self.state.last_saveload, + callback = self.master.server_playback_path ) def keypress(self, (maxcol,), key): @@ -175,23 +175,23 @@ class ConnectionItem(urwid.WidgetWrap): self.master.sync_list_view() elif key == "S": if not self.master.server_playback: - self.master.prompt_onekey( - "Server Replay", - ( + signals.status_prompt_onekey.send( + prompt = "Server Replay", + keys = ( ("all flows", "a"), ("this flow", "t"), ("file", "f"), ), - self.server_replay_prompt, + callback = self.server_replay_prompt, ) else: - self.master.prompt_onekey( - "Stop current server replay?", - ( + signals.status_prompt_onekey.send( + prompt = "Stop current server replay?", + keys = ( ("yes", "y"), ("no", "n"), ), - self.stop_server_playback_prompt, + callback = self.stop_server_playback_prompt, ) elif key == "V": if not self.flow.modified(): @@ -201,13 +201,14 @@ class ConnectionItem(urwid.WidgetWrap): self.master.sync_list_view() signals.status_message.send(message="Reverted.") elif key == "w": - self.master.prompt_onekey( - "Save", - ( + signals.status_prompt_onekey.send( + self, + prompt = "Save", + keys = ( ("all flows", "a"), ("this flow", "t"), ), - self.save_flows_prompt, + callback = self.save_flows_prompt, ) elif key == "X": self.flow.kill(self.master) @@ -215,11 +216,11 @@ class ConnectionItem(urwid.WidgetWrap): if self.flow.request: self.master.view_flow(self.flow) elif key == "|": - self.master.path_prompt( - "Send flow to script: ", - self.state.last_script, - self.master.run_script_once, - self.flow + signals.status_path_prompt.send( + prompt = "Send flow to script: ", + text = self.state.last_script, + callback = self.master.run_script_once, + args = (self.flow,) ) elif key == "g": common.ask_copy_part("a", self.flow, self.master, self.state) @@ -266,7 +267,12 @@ class FlowListBox(urwid.ListBox): def get_method(self, k): if k == "e": - self.master.prompt("Method:", "", self.get_method_raw) + signals.status_prompt.send( + self, + prompt = "Method:", + text = "", + callback = self.get_method_raw + ) else: method = "" for i in common.METHOD_OPTIONS: @@ -275,11 +281,11 @@ class FlowListBox(urwid.ListBox): self.get_url(method) def get_url(self, method): - self.master.prompt( - "URL:", - "http://www.example.com/", - self.new_request, - method + signals.status_prompt.send( + prompt = "URL:", + text = "http://www.example.com/", + callback = self.new_request, + args = (method,) ) def new_request(self, url, method): @@ -301,22 +307,23 @@ class FlowListBox(urwid.ListBox): elif key == "e": self.master.toggle_eventlog() elif key == "l": - self.master.prompt( - "Limit: ", - self.master.state.limit_txt, - self.master.set_limit + signals.status_prompt.send( + prompt = "Limit: ", + text = self.master.state.limit_txt, + callback = self.master.set_limit ) elif key == "L": - self.master.path_prompt( - "Load flows: ", - self.master.state.last_saveload, - self.master.load_flows_callback + signals.status_path_prompt.send( + self, + prompt = "Load flows: ", + text = self.master.state.last_saveload, + callback = self.master.load_flows_callback ) elif key == "n": - self.master.prompt_onekey( - "Method", - common.METHOD_OPTIONS, - self.get_method + signals.status_prompt_onekey.send( + prompt = "Method", + keys = common.METHOD_OPTIONS, + callback = self.get_method ) elif key == "F": self.master.toggle_follow_flows() @@ -324,10 +331,11 @@ class FlowListBox(urwid.ListBox): if self.master.stream: self.master.stop_stream() else: - self.master.path_prompt( - "Stream flows to: ", - self.master.state.last_saveload, - self.master.start_stream_to_path + signals.status_path_prompt.send( + self, + prompt = "Stream flows to: ", + text = self.master.state.last_saveload, + callback = self.master.start_stream_to_path ) else: return urwid.ListBox.keypress(self, size, key) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index b22bbb37..941ceb94 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -492,7 +492,11 @@ class FlowView(urwid.WidgetWrap): def edit_method(self, m): if m == "e": - self.master.prompt_edit("Method", self.flow.request.method, self.set_method_raw) + signals.status_prompt.send( + prompt = "Method: ", + text = self.flow.request.method, + callback = self.set_method_raw + ) else: for i in common.METHOD_OPTIONS: if i[1] == m: @@ -567,14 +571,14 @@ class FlowView(urwid.WidgetWrap): message.content = c.rstrip("\n") elif part == "f": if not message.get_form_urlencoded() and message.content: - self.master.prompt_onekey( - "Existing body is not a URL-encoded form. Clear and edit?", - [ + signals.status_prompt_onekey.send( + prompt = "Existing body is not a URL-encoded form. Clear and edit?", + keys = [ ("yes", "y"), ("no", "n"), ], - self.edit_form_confirm, - message + callback = self.edit_form_confirm, + args = (message,) ) else: self.edit_form(message) @@ -587,13 +591,29 @@ class FlowView(urwid.WidgetWrap): elif part == "q": self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_edit("URL", message.url, self.set_url) + signals.status_prompt.send( + prompt = "URL: ", + text = message.url, + callback = self.set_url + ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_onekey("Method", common.METHOD_OPTIONS, self.edit_method) + signals.status_prompt_onekey.send( + prompt = "Method", + keys = common.METHOD_OPTIONS, + callback = self.edit_method + ) elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: - self.master.prompt_edit("Code", str(message.code), self.set_resp_code) + signals.status_prompt.send( + prompt = "Code: ", + text = str(message.code), + callback = self.set_resp_code + ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: - self.master.prompt_edit("Message", message.msg, self.set_resp_msg) + signals.status_prompt.send( + prompt = "Message: ", + text = message.msg, + callback = self.set_resp_msg + ) self.master.refresh_flow(self.flow) def _view_nextprev_flow(self, np, flow): @@ -684,9 +704,9 @@ class FlowView(urwid.WidgetWrap): signals.status_message.send(message="Duplicated.") elif key == "e": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: - self.master.prompt_onekey( - "Edit request", - ( + signals.status_prompt_onekey.send( + prompt = "Edit request", + keys = ( ("query", "q"), ("path", "p"), ("url", "u"), @@ -695,18 +715,18 @@ class FlowView(urwid.WidgetWrap): ("raw body", "r"), ("method", "m"), ), - self.edit + callback = self.edit ) else: - self.master.prompt_onekey( - "Edit response", - ( + signals.status_prompt_onekey.send( + prompt = "Edit response", + keys = ( ("code", "c"), ("message", "m"), ("header", "h"), ("raw body", "r"), ), - self.edit + callback = self.edit ) key = None elif key == "f": @@ -727,10 +747,11 @@ class FlowView(urwid.WidgetWrap): elif key == "m": p = list(contentview.view_prompts) p.insert(0, ("Clear", "C")) - self.master.prompt_onekey( - "Display mode", - p, - self.change_this_display_mode + signals.status_prompt_onekey.send( + self, + prompt = "Display mode", + keys = p, + callback = self.change_this_display_mode ) key = None elif key == "p": @@ -748,11 +769,11 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) signals.status_message.send(message="Reverted.") elif key == "W": - self.master.path_prompt( - "Save this flow: ", - self.state.last_saveload, - self.master.save_one_flow, - self.flow + signals.status_path_prompt.send( + prompt = "Save this flow: ", + text = self.state.last_saveload, + callback = self.master.save_one_flow, + args = (self.flow,) ) elif key == "v": if conn and conn.content: @@ -763,18 +784,20 @@ class FlowView(urwid.WidgetWrap): else: signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": - self.master.path_prompt( - "Send flow to script: ", self.state.last_script, - self.master.run_script_once, self.flow + signals.status_path_prompt.send( + prompt = "Send flow to script: ", + text = self.state.last_script, + callback = self.master.run_script_once, + args = (self.flow,) ) elif key == "x": - self.master.prompt_onekey( - "Delete body", - ( + signals.status_prompt_onekey.send( + prompt = "Delete body", + keys = ( ("completely", "c"), ("mark as missing", "m"), ), - self.delete_body + callback = self.delete_body ) key = None elif key == "X": @@ -787,22 +810,24 @@ class FlowView(urwid.WidgetWrap): if not conn.decode(): signals.status_message.send(message="Could not decode - invalid data?") else: - self.master.prompt_onekey( - "Select encoding: ", - ( + signals.status_prompt_onekey.send( + prompt = "Select encoding: ", + keys = ( ("gzip", "z"), ("deflate", "d"), ), - self.encode_callback, - conn + callback = self.encode_callback, + args = (conn,) ) self.master.refresh_flow(self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") search_prompt = "Search body ["+last_search_string+"]: " if last_search_string else "Search body: " - self.master.prompt(search_prompt, - None, - self.search) + signals.status_prompt.send( + prompt = search_prompt, + text = "", + callback = self.search + ) elif key == "n": self.search_again(backwards=False) elif key == "N": diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index 0b563c52..eb66e59e 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -338,11 +338,20 @@ class GridEditor(urwid.WidgetWrap): self.walker.delete_focus() elif key == "r": if self.walker.get_current_value() is not None: - self.master.path_prompt("Read file: ", "", self.read_file) + signals.status_path_prompt.send( + self, + prompt = "Read file: ", + text = "", + callback = self.read_file + ) elif key == "R": if self.walker.get_current_value() is not None: - self.master.path_prompt( - "Read unescaped file: ", "", self.read_file, True + signals.status_path_prompt.send( + self, + prompt = "Read unescaped file: ", + text = "", + callback = self.read_file, + args = (True,) ) elif key == "e": o = self.walker.get_current_value() @@ -431,10 +440,10 @@ class HeaderEditor(GridEditor): def handle_key(self, key): if key == "U": - self.master.prompt_onekey( - "Add User-Agent header:", - [(i[0], i[1]) for i in http_uastrings.UASTRINGS], - self.set_user_agent, + signals.status_prompt_onekey.send( + prompt = "Add User-Agent header:", + keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS], + callback = self.set_user_agent, ) return True @@ -500,10 +509,10 @@ class SetHeadersEditor(GridEditor): def handle_key(self, key): if key == "U": - self.master.prompt_onekey( - "Add User-Agent header:", - [(i[0], i[1]) for i in http_uastrings.UASTRINGS], - self.set_user_agent, + signals.status_prompt_onekey.send( + prompt = "Add User-Agent header:", + keys = [(i[0], i[1]) for i in http_uastrings.UASTRINGS], + callback = self.set_user_agent, ) return True diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 7b0ec937..8fb35cff 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -1,5 +1,19 @@ - import blinker +# Show a status message in the action bar status_message = blinker.Signal() + +# Prompt for input +status_prompt = blinker.Signal() + +# Prompt for a path +status_path_prompt = blinker.Signal() + +# Prompt for a single keystroke +status_prompt_onekey = blinker.Signal() + +# Call a callback in N seconds call_in = blinker.Signal() + +# Focus the body, footer or header of the main window +focus = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index a29767e4..c1a907bd 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -2,7 +2,7 @@ import time import urwid -from . import pathedit, signals +from . import pathedit, signals, common from .. import utils @@ -11,18 +11,12 @@ class ActionBar(urwid.WidgetWrap): urwid.WidgetWrap.__init__(self, None) self.clear() signals.status_message.connect(self.sig_message) + signals.status_prompt.connect(self.sig_prompt) + signals.status_path_prompt.connect(self.sig_path_prompt) + signals.status_prompt_onekey.connect(self.sig_prompt_onekey) - def clear(self): - self._w = urwid.Text("") - - def selectable(self): - return True - - def path_prompt(self, prompt, text): - self._w = pathedit.PathEdit(prompt, text) - - def prompt(self, prompt, text = ""): - self._w = urwid.Edit(prompt, text or "") + self.prompting = False + self.onekey = False def sig_message(self, sender, message, expire=None): w = urwid.Text(message) @@ -33,6 +27,72 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.call_in.send(seconds=expire, callback=cb) + def sig_prompt(self, sender, prompt, text, callback, args=()): + signals.focus.send(self, section="footer") + self._w = urwid.Edit(prompt, text or "") + self.prompting = (callback, args) + + def sig_path_prompt(self, sender, prompt, text, callback, args=()): + signals.focus.send(self, section="footer") + self._w = pathedit.PathEdit(prompt, text) + self.prompting = (callback, args) + + def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): + """ + Keys are a set of (word, key) tuples. The appropriate key in the + word is highlighted. + """ + signals.focus.send(self, section="footer") + prompt = [prompt, " ("] + mkup = [] + for i, e in enumerate(keys): + mkup.extend(common.highlight_key(e[0], e[1])) + if i < len(keys)-1: + mkup.append(",") + prompt.extend(mkup) + prompt.append(")? ") + self.onekey = set(i[1] for i in keys) + self._w = urwid.Edit(prompt, "") + self.prompting = (callback, args) + + def selectable(self): + return True + + def keypress(self, size, k): + if self.prompting: + if k == "esc": + self.prompt_done() + elif self.onekey: + if k == "enter": + self.prompt_done() + elif k in self.onekey: + self.prompt_execute(k) + elif k == "enter": + self.prompt_execute() + else: + if common.is_keypress(k): + self._w.keypress(size, k) + else: + return k + + def clear(self): + self._w = urwid.Text("") + + def prompt_done(self): + self.prompting = False + self.onekey = False + signals.status_message.send(message="") + signals.focus.send(self, section="body") + + def prompt_execute(self, txt=None): + if not txt: + txt = self._w.get_edit_text() + p, args = self.prompting + self.prompt_done() + msg = p(txt, *args) + if msg: + signals.status_message.send(message=msg, expire=1) + class StatusBar(urwid.WidgetWrap): def __init__(self, master, helptext): diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 44a5a316..55145c48 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -1,151 +1,144 @@ import urwid -from . import common, grideditor +from . import common, grideditor, signals, contentview class Window(urwid.Frame): def __init__(self, master, body, header, footer): urwid.Frame.__init__(self, body, header=header, footer=footer) self.master = master + signals.focus.connect(self.sig_focus) + + def sig_focus(self, sender, section): + self.focus_position = section def keypress(self, size, k): - if self.master.prompting: - if k == "esc": - self.master.prompt_cancel() - elif self.master.onekey: - if k == "enter": - self.master.prompt_cancel() - elif k in self.master.onekey: - self.master.prompt_execute(k) - elif k == "enter": - self.master.prompt_execute() - else: - if common.is_keypress(k): - urwid.Frame.keypress(self, self.master.loop.screen_size, k) - else: - return k - else: - k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) - if k == "?": - self.master.view_help() - elif k == "c": - if not self.master.client_playback: - self.master.path_prompt( - "Client replay: ", - self.master.state.last_saveload, - self.master.client_playback_path - ) - else: - self.master.prompt_onekey( - "Stop current client replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.master.stop_client_playback_prompt, - ) - elif k == "H": - self.master.view_grideditor( - grideditor.SetHeadersEditor( - self.master, - self.master.setheaders.get_specs(), - self.master.setheaders.set - ) - ) - elif k == "I": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_ignore_filter()], - self.master.edit_ignore_filter - ) + k = urwid.Frame.keypress(self, self.master.loop.screen_size, k) + if k == "?": + self.master.view_help() + elif k == "c": + if not self.master.client_playback: + signals.status_path_prompt.send( + self, + prompt = "Client replay: ", + text = self.master.state.last_saveload, + callback = self.master.client_playback_path ) - elif k == "T": - self.master.view_grideditor( - grideditor.HostPatternEditor( - self.master, - [[x] for x in self.master.get_tcp_filter()], - self.master.edit_tcp_filter - ) - ) - elif k == "i": - self.master.prompt( - "Intercept filter: ", - self.master.state.intercept_txt, - self.master.set_intercept - ) - elif k == "Q": - raise urwid.ExitMainLoop - elif k == "q": - self.master.prompt_onekey( - "Quit", - ( + else: + signals.status_prompt_onekey.send( + self, + prompt = "Stop current client replay?", + keys = ( ("yes", "y"), ("no", "n"), ), - self.master.quit, + callback = self.master.stop_client_playback_prompt, ) - elif k == "M": - self.master.prompt_onekey( - "Global default display mode", - contentview.view_prompts, - self.master.change_default_display_mode + elif k == "H": + self.master.view_grideditor( + grideditor.SetHeadersEditor( + self.master, + self.master.setheaders.get_specs(), + self.master.setheaders.set ) - elif k == "R": - self.master.view_grideditor( - grideditor.ReplaceEditor( - self.master, - self.master.replacehooks.get_specs(), - self.master.replacehooks.set - ) + ) + elif k == "I": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_ignore_filter()], + self.master.edit_ignore_filter ) - elif k == "s": - self.master.view_grideditor( - grideditor.ScriptEditor( - self.master, - [[i.command] for i in self.master.scripts], - self.master.edit_scripts - ) + ) + elif k == "T": + self.master.view_grideditor( + grideditor.HostPatternEditor( + self.master, + [[x] for x in self.master.get_tcp_filter()], + self.master.edit_tcp_filter ) - elif k == "S": - if not self.master.server_playback: - self.master.path_prompt( - "Server replay path: ", - self.master.state.last_saveload, - self.master.server_playback_path - ) - else: - self.master.prompt_onekey( - "Stop current server replay?", - ( - ("yes", "y"), - ("no", "n"), - ), - self.master.stop_server_playback_prompt, - ) - elif k == "o": - self.master.prompt_onekey( - "Options", - ( - ("anticache", "a"), - ("anticomp", "c"), - ("showhost", "h"), - ("killextra", "k"), - ("norefresh", "n"), - ("no-upstream-certs", "u"), - ), - self.master._change_options + ) + elif k == "i": + signals.status_prompt.send( + self, + prompt = "Intercept filter: ", + text = self.master.state.intercept_txt, + callback = self.master.set_intercept + ) + elif k == "Q": + raise urwid.ExitMainLoop + elif k == "q": + signals.status_prompt_onekey.send( + self, + prompt = "Quit", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = self.master.quit, + ) + elif k == "M": + signals.status_prompt_onekey.send( + prompt = "Global default display mode", + keys = contentview.view_prompts, + callback = self.master.change_default_display_mode + ) + elif k == "R": + self.master.view_grideditor( + grideditor.ReplaceEditor( + self.master, + self.master.replacehooks.get_specs(), + self.master.replacehooks.set ) - elif k == "t": - self.master.prompt( - "Sticky cookie filter: ", - self.master.stickycookie_txt, - self.master.set_stickycookie + ) + elif k == "s": + self.master.view_grideditor( + grideditor.ScriptEditor( + self.master, + [[i.command] for i in self.master.scripts], + self.master.edit_scripts ) - elif k == "u": - self.master.prompt( - "Sticky auth filter: ", - self.master.stickyauth_txt, - self.master.set_stickyauth + ) + elif k == "S": + if not self.master.server_playback: + signals.status_path_prompt.send( + self, + prompt = "Server replay path: ", + text = self.master.state.last_saveload, + callback = self.master.server_playback_path ) else: - return k - self.footer.redraw() + signals.status_prompt_onekey.send( + self, + prompt = "Stop current server replay?", + keys = ( + ("yes", "y"), + ("no", "n"), + ), + callback = self.master.stop_server_playback_prompt, + ) + elif k == "o": + signals.status_prompt_onekey.send( + prompt = "Options", + keys = ( + ("anticache", "a"), + ("anticomp", "c"), + ("showhost", "h"), + ("killextra", "k"), + ("norefresh", "n"), + ("no-upstream-certs", "u"), + ), + callback = self.master._change_options + ) + elif k == "t": + signals.status_prompt.send( + prompt = "Sticky cookie filter: ", + text = self.master.stickycookie_txt, + callback = self.master.set_stickycookie + ) + elif k == "u": + signals.status_prompt.send( + prompt = "Sticky auth filter: ", + text = self.master.stickyauth_txt, + callback = self.master.set_stickyauth + ) + else: + return k diff --git a/test/tools/testpatt b/test/tools/testpatt index f6d1169b..d4546d48 100755 --- a/test/tools/testpatt +++ b/test/tools/testpatt @@ -2,8 +2,8 @@ # Generate a test pattern with pathoc PATHOD=http://localhost:9999 -pathoc localhost:8080 "get:'$PATHOD/p/200:p0,1:b@200b'" -pathoc localhost:8080 "get:'$PATHOD/p/300:p0,1:b@200b'" -pathoc localhost:8080 "get:'$PATHOD/p/400:p0,1:b@200b'" -pathoc localhost:8080 "get:'$PATHOD/p/500:p0,1:b@200b'" -pathoc localhost:8080 "get:'$PATHOD/p/600:p0,1:b@200b'" +pathoc localhost:8080 "get:'$PATHOD/p/200:p0,1:b@200b':b@200b" +pathoc localhost:8080 "get:'$PATHOD/p/300:p0,1:b@200b':b@200b" +pathoc localhost:8080 "get:'$PATHOD/p/400:p0,1:b@200b':b@200b" +pathoc localhost:8080 "get:'$PATHOD/p/500:p0,1:b@200b':b@200b" +pathoc localhost:8080 "get:'$PATHOD/p/600:p0,1:b@200b':b@200b" -- cgit v1.2.3 From 572000aa039a789ba35d4ef14e0c096256d6997d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 13:59:34 +1300 Subject: Rationalise prompt calling conventions --- libmproxy/console/common.py | 8 ++++---- libmproxy/console/flowlist.py | 30 +++++++++++++++--------------- libmproxy/console/flowview.py | 18 +++++++++--------- libmproxy/console/grideditor.py | 8 ++++---- libmproxy/console/signals.py | 2 +- libmproxy/console/statusbar.py | 18 ++++++------------ libmproxy/console/window.py | 14 +++++++------- 7 files changed, 46 insertions(+), 52 deletions(-) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index 185480db..e4ecde91 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -203,7 +203,7 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): - signals.status_path_prompt.send( + signals.status_prompt_path.send( prompt = prompt, text = state.last_saveload, callback = save_data, @@ -260,7 +260,7 @@ def copy_flow(part, scope, flow, master, state): except RuntimeError: def save(k): if k == "y": - ask_save_path("Save data: ", data, master, state) + ask_save_path("Save data", data, master, state) signals.status_prompt_onekey.send( prompt = "Cannot copy binary data to clipboard. Save as file?", keys = ( @@ -316,14 +316,14 @@ def ask_save_body(part, master, state, flow): elif part == "q" and request_has_content: ask_save_path( - "Save request content: ", + "Save request content", flow.request.get_decoded_content(), master, state ) elif part == "s" and response_has_content: ask_save_path( - "Save response content: ", + "Save response content", flow.response.get_decoded_content(), master, state diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index d4dd89d8..f39188bb 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -111,14 +111,14 @@ class ConnectionItem(urwid.WidgetWrap): def save_flows_prompt(self, k): if k == "a": - signals.status_path_prompt.send( - prompt = "Save all flows to: ", + signals.status_prompt_path.send( + prompt = "Save all flows to", text = self.state.last_saveload, callback = self.master.save_flows ) else: - signals.status_path_prompt.send( - prompt = "Save this flow to: ", + signals.status_prompt_path.send( + prompt = "Save this flow to", text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) @@ -150,8 +150,8 @@ class ConnectionItem(urwid.WidgetWrap): self.master.options.replay_ignore_host ) else: - signals.status_path_prompt.send( - prompt = "Server replay path: ", + signals.status_prompt_path.send( + prompt = "Server replay path", text = self.state.last_saveload, callback = self.master.server_playback_path ) @@ -216,8 +216,8 @@ class ConnectionItem(urwid.WidgetWrap): if self.flow.request: self.master.view_flow(self.flow) elif key == "|": - signals.status_path_prompt.send( - prompt = "Send flow to script: ", + signals.status_prompt_path.send( + prompt = "Send flow to script", text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) @@ -269,7 +269,7 @@ class FlowListBox(urwid.ListBox): if k == "e": signals.status_prompt.send( self, - prompt = "Method:", + prompt = "Method", text = "", callback = self.get_method_raw ) @@ -282,7 +282,7 @@ class FlowListBox(urwid.ListBox): def get_url(self, method): signals.status_prompt.send( - prompt = "URL:", + prompt = "URL", text = "http://www.example.com/", callback = self.new_request, args = (method,) @@ -308,14 +308,14 @@ class FlowListBox(urwid.ListBox): self.master.toggle_eventlog() elif key == "l": signals.status_prompt.send( - prompt = "Limit: ", + prompt = "Limit", text = self.master.state.limit_txt, callback = self.master.set_limit ) elif key == "L": - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Load flows: ", + prompt = "Load flows", text = self.master.state.last_saveload, callback = self.master.load_flows_callback ) @@ -331,9 +331,9 @@ class FlowListBox(urwid.ListBox): if self.master.stream: self.master.stop_stream() else: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Stream flows to: ", + prompt = "Stream flows to", text = self.master.state.last_saveload, callback = self.master.start_stream_to_path ) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 941ceb94..b9d5fbca 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -493,7 +493,7 @@ class FlowView(urwid.WidgetWrap): def edit_method(self, m): if m == "e": signals.status_prompt.send( - prompt = "Method: ", + prompt = "Method", text = self.flow.request.method, callback = self.set_method_raw ) @@ -592,7 +592,7 @@ class FlowView(urwid.WidgetWrap): self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: signals.status_prompt.send( - prompt = "URL: ", + prompt = "URL", text = message.url, callback = self.set_url ) @@ -604,13 +604,13 @@ class FlowView(urwid.WidgetWrap): ) elif part == "c" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: signals.status_prompt.send( - prompt = "Code: ", + prompt = "Code", text = str(message.code), callback = self.set_resp_code ) elif part == "m" and self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE: signals.status_prompt.send( - prompt = "Message: ", + prompt = "Message", text = message.msg, callback = self.set_resp_msg ) @@ -769,8 +769,8 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) signals.status_message.send(message="Reverted.") elif key == "W": - signals.status_path_prompt.send( - prompt = "Save this flow: ", + signals.status_prompt_path.send( + prompt = "Save this flow", text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) @@ -784,8 +784,8 @@ class FlowView(urwid.WidgetWrap): else: signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") elif key == "|": - signals.status_path_prompt.send( - prompt = "Send flow to script: ", + signals.status_prompt_path.send( + prompt = "Send flow to script", text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) @@ -822,7 +822,7 @@ class FlowView(urwid.WidgetWrap): self.master.refresh_flow(self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") - search_prompt = "Search body ["+last_search_string+"]: " if last_search_string else "Search body: " + search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" signals.status_prompt.send( prompt = search_prompt, text = "", diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index eb66e59e..e7c9854b 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -338,17 +338,17 @@ class GridEditor(urwid.WidgetWrap): self.walker.delete_focus() elif key == "r": if self.walker.get_current_value() is not None: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Read file: ", + prompt = "Read file", text = "", callback = self.read_file ) elif key == "R": if self.walker.get_current_value() is not None: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Read unescaped file: ", + prompt = "Read unescaped file", text = "", callback = self.read_file, args = (True,) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 8fb35cff..e8944afb 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -7,7 +7,7 @@ status_message = blinker.Signal() status_prompt = blinker.Signal() # Prompt for a path -status_path_prompt = blinker.Signal() +status_prompt_path = blinker.Signal() # Prompt for a single keystroke status_prompt_onekey = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index c1a907bd..7ff26b15 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -12,7 +12,7 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.status_message.connect(self.sig_message) signals.status_prompt.connect(self.sig_prompt) - signals.status_path_prompt.connect(self.sig_path_prompt) + signals.status_prompt_path.connect(self.sig_path_prompt) signals.status_prompt_onekey.connect(self.sig_prompt_onekey) self.prompting = False @@ -27,14 +27,17 @@ class ActionBar(urwid.WidgetWrap): self.clear() signals.call_in.send(seconds=expire, callback=cb) + def prep_prompt(self, p): + return p.strip() + ": " + def sig_prompt(self, sender, prompt, text, callback, args=()): signals.focus.send(self, section="footer") - self._w = urwid.Edit(prompt, text or "") + self._w = urwid.Edit(self.prep_prompt(prompt), text or "") self.prompting = (callback, args) def sig_path_prompt(self, sender, prompt, text, callback, args=()): signals.focus.send(self, section="footer") - self._w = pathedit.PathEdit(prompt, text) + self._w = pathedit.PathEdit(self.prep_prompt(prompt), text) self.prompting = (callback, args) def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): @@ -230,12 +233,3 @@ class StatusBar(urwid.WidgetWrap): def selectable(self): return True - - def get_edit_text(self): - return self.ab._w.get_edit_text() - - def path_prompt(self, prompt, text): - return self.ab.path_prompt(prompt, text) - - def prompt(self, prompt, text = ""): - self.ab.prompt(prompt, text) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 55145c48..87f06637 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -16,9 +16,9 @@ class Window(urwid.Frame): self.master.view_help() elif k == "c": if not self.master.client_playback: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Client replay: ", + prompt = "Client replay", text = self.master.state.last_saveload, callback = self.master.client_playback_path ) @@ -59,7 +59,7 @@ class Window(urwid.Frame): elif k == "i": signals.status_prompt.send( self, - prompt = "Intercept filter: ", + prompt = "Intercept filter", text = self.master.state.intercept_txt, callback = self.master.set_intercept ) @@ -99,9 +99,9 @@ class Window(urwid.Frame): ) elif k == "S": if not self.master.server_playback: - signals.status_path_prompt.send( + signals.status_prompt_path.send( self, - prompt = "Server replay path: ", + prompt = "Server replay path", text = self.master.state.last_saveload, callback = self.master.server_playback_path ) @@ -130,13 +130,13 @@ class Window(urwid.Frame): ) elif k == "t": signals.status_prompt.send( - prompt = "Sticky cookie filter: ", + prompt = "Sticky cookie filter", text = self.master.stickycookie_txt, callback = self.master.set_stickycookie ) elif k == "u": signals.status_prompt.send( - prompt = "Sticky auth filter: ", + prompt = "Sticky auth filter", text = self.master.stickyauth_txt, callback = self.master.set_stickyauth ) -- cgit v1.2.3 From 200498e7aa57effd7158c8d735f95c6556203a07 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 14:14:44 +1300 Subject: Simplify the way in which path prompts keep state In the past, we kept the last path the user specified for a number of different path types to pre-seed the path prompt. Now, we no longer distinguish between types, and pre-seed with the last used directory regardless. --- libmproxy/console/__init__.py | 6 ------ libmproxy/console/common.py | 2 -- libmproxy/console/flowlist.py | 6 ------ libmproxy/console/flowview.py | 2 -- libmproxy/console/grideditor.py | 2 -- libmproxy/console/statusbar.py | 22 ++++++++++++++++------ libmproxy/console/window.py | 2 -- 7 files changed, 16 insertions(+), 26 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index d8eb8a41..34abe6f4 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -31,8 +31,6 @@ class ConsoleState(flow.State): self.view_mode = common.VIEW_LIST self.view_flow_mode = common.VIEW_FLOW_REQUEST - self.last_script = "" - self.last_saveload = "" self.flowsettings = weakref.WeakKeyDictionary() def add_flow_setting(self, flow, key, value): @@ -258,7 +256,6 @@ class ConsoleMaster(flow.FlowMaster): self._run_script_method("error", s, f) s.unload() self.refresh_flow(f) - self.state.last_script = command def set_script(self, command): if not command: @@ -266,7 +263,6 @@ class ConsoleMaster(flow.FlowMaster): ret = self.load_script(command) if ret: signals.status_message.send(message=ret) - self.state.last_script = command def toggle_eventlog(self): self.eventlog = not self.eventlog @@ -501,7 +497,6 @@ class ConsoleMaster(flow.FlowMaster): self.help_context = flowview.help_context def _write_flows(self, path, flows): - self.state.last_saveload = path if not path: return path = os.path.expanduser(path) @@ -527,7 +522,6 @@ class ConsoleMaster(flow.FlowMaster): return ret or "Flows loaded from %s"%path def load_flows_path(self, path): - self.state.last_saveload = path reterr = None try: flow.FlowMaster.load_flows_file(self, path) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index e4ecde91..c0593af4 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -193,7 +193,6 @@ def raw_format_flow(f, focus, extended, padding): def save_data(path, data, master, state): if not path: return - state.last_saveload = path path = os.path.expanduser(path) try: with file(path, "wb") as f: @@ -205,7 +204,6 @@ def save_data(path, data, master, state): def ask_save_path(prompt, data, master, state): signals.status_prompt_path.send( prompt = prompt, - text = state.last_saveload, callback = save_data, args = (data, master, state) ) diff --git a/libmproxy/console/flowlist.py b/libmproxy/console/flowlist.py index f39188bb..946bd97b 100644 --- a/libmproxy/console/flowlist.py +++ b/libmproxy/console/flowlist.py @@ -113,13 +113,11 @@ class ConnectionItem(urwid.WidgetWrap): if k == "a": signals.status_prompt_path.send( prompt = "Save all flows to", - text = self.state.last_saveload, callback = self.master.save_flows ) else: signals.status_prompt_path.send( prompt = "Save this flow to", - text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) ) @@ -152,7 +150,6 @@ class ConnectionItem(urwid.WidgetWrap): else: signals.status_prompt_path.send( prompt = "Server replay path", - text = self.state.last_saveload, callback = self.master.server_playback_path ) @@ -218,7 +215,6 @@ class ConnectionItem(urwid.WidgetWrap): elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", - text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) ) @@ -316,7 +312,6 @@ class FlowListBox(urwid.ListBox): signals.status_prompt_path.send( self, prompt = "Load flows", - text = self.master.state.last_saveload, callback = self.master.load_flows_callback ) elif key == "n": @@ -334,7 +329,6 @@ class FlowListBox(urwid.ListBox): signals.status_prompt_path.send( self, prompt = "Stream flows to", - text = self.master.state.last_saveload, callback = self.master.start_stream_to_path ) else: diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index b9d5fbca..d63b8a8c 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -771,7 +771,6 @@ class FlowView(urwid.WidgetWrap): elif key == "W": signals.status_prompt_path.send( prompt = "Save this flow", - text = self.state.last_saveload, callback = self.master.save_one_flow, args = (self.flow,) ) @@ -786,7 +785,6 @@ class FlowView(urwid.WidgetWrap): elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", - text = self.state.last_script, callback = self.master.run_script_once, args = (self.flow,) ) diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index e7c9854b..dc3bad0e 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -341,7 +341,6 @@ class GridEditor(urwid.WidgetWrap): signals.status_prompt_path.send( self, prompt = "Read file", - text = "", callback = self.read_file ) elif key == "R": @@ -349,7 +348,6 @@ class GridEditor(urwid.WidgetWrap): signals.status_prompt_path.send( self, prompt = "Read unescaped file", - text = "", callback = self.read_file, args = (True,) ) diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7ff26b15..30819188 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -1,4 +1,5 @@ import time +import os.path import urwid @@ -15,8 +16,12 @@ class ActionBar(urwid.WidgetWrap): signals.status_prompt_path.connect(self.sig_path_prompt) signals.status_prompt_onekey.connect(self.sig_prompt_onekey) + self.last_path = "" + self.prompting = False self.onekey = False + self.pathprompt = False + def sig_message(self, sender, message, expire=None): w = urwid.Text(message) @@ -35,9 +40,13 @@ class ActionBar(urwid.WidgetWrap): self._w = urwid.Edit(self.prep_prompt(prompt), text or "") self.prompting = (callback, args) - def sig_path_prompt(self, sender, prompt, text, callback, args=()): + def sig_path_prompt(self, sender, prompt, callback, args=()): signals.focus.send(self, section="footer") - self._w = pathedit.PathEdit(self.prep_prompt(prompt), text) + self._w = pathedit.PathEdit( + self.prep_prompt(prompt), + os.path.dirname(self.last_path) + ) + self.pathprompt = True self.prompting = (callback, args) def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()): @@ -71,7 +80,7 @@ class ActionBar(urwid.WidgetWrap): elif k in self.onekey: self.prompt_execute(k) elif k == "enter": - self.prompt_execute() + self.prompt_execute(self._w.get_edit_text()) else: if common.is_keypress(k): self._w.keypress(size, k) @@ -84,12 +93,13 @@ class ActionBar(urwid.WidgetWrap): def prompt_done(self): self.prompting = False self.onekey = False + self.pathprompt = False signals.status_message.send(message="") signals.focus.send(self, section="body") - def prompt_execute(self, txt=None): - if not txt: - txt = self._w.get_edit_text() + def prompt_execute(self, txt): + if self.pathprompt: + self.last_path = txt p, args = self.prompting self.prompt_done() msg = p(txt, *args) diff --git a/libmproxy/console/window.py b/libmproxy/console/window.py index 87f06637..d686f61d 100644 --- a/libmproxy/console/window.py +++ b/libmproxy/console/window.py @@ -19,7 +19,6 @@ class Window(urwid.Frame): signals.status_prompt_path.send( self, prompt = "Client replay", - text = self.master.state.last_saveload, callback = self.master.client_playback_path ) else: @@ -102,7 +101,6 @@ class Window(urwid.Frame): signals.status_prompt_path.send( self, prompt = "Server replay path", - text = self.master.state.last_saveload, callback = self.master.server_playback_path ) else: -- cgit v1.2.3 From 941584623281905fec22d8857c5501d196c051f7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 02:25:47 +0100 Subject: web: raw content view --- libmproxy/web/static/app.css | 3 +- libmproxy/web/static/app.js | 58 ++++++++++++++++++++++++--- web/src/css/flowdetail.less | 3 +- web/src/js/components/flowview/contentview.js | 43 +++++++++++++++++--- web/src/js/flow/utils.js | 13 ++++++ 5 files changed, 107 insertions(+), 13 deletions(-) diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index 91e847a4..ccaefe92 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -271,7 +271,8 @@ header .menu { } .flow-detail { width: 100%; - overflow: auto; + overflow-x: auto; + overflow-y: scroll; } .flow-detail nav { background-color: #F2F2F2; diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index eb8ef45e..2254b415 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -1195,23 +1195,56 @@ var Image = React.createClass({displayName: "Image", } }, render: function () { - var message_name = this.props.flow.request === this.props.message ? "request" : "response"; - var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + var url = MessageUtils.getContentURL(this.props.flow, this.props.message); return React.createElement("div", {className: "flowview-image"}, React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) ); } }); +var RawMixin = { + getInitialState: function () { + return { + content: undefined + } + }, + requestContent: function (nextProps) { + this.setState({content: undefined}); + var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + request.done(function (data) { + this.setState({content: data}); + }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { + this.setState({content: "AJAX Error: " + textStatus}); + }.bind(this)); + + }, + componentWillMount: function () { + this.requestContent(this.props); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps); + } + }, + render: function () { + if (!this.state.content) { + return React.createElement("div", {className: "text-center"}, + React.createElement("i", {className: "fa fa-spinner fa-spin"}) + ); + } + return this.renderContent(); + } +}; + var Raw = React.createClass({displayName: "Raw", + mixins: [RawMixin], statics: { matches: function (message) { return true; } }, - render: function () { - //FIXME - return React.createElement("div", null, "raw"); + renderContent: function () { + return React.createElement("pre", null, this.state.content); } }); @@ -4426,6 +4459,7 @@ module.exports = (function() { },{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); +var $ = require("jquery"); var MessageUtils = { getContentType: function (message) { @@ -4461,6 +4495,18 @@ var MessageUtils = { } } return false; + }, + getContentURL: function(flow, message){ + if(message === flow.request){ + message = "request"; + } else if (message === flow.response){ + message = "response"; + } + return "/flows/" + flow.id + "/" + message + "/content"; + }, + getContent: function(flow, message){ + var url = MessageUtils.getContentURL(flow, message); + return $.get(url); } }; @@ -4492,7 +4538,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],22:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); diff --git a/web/src/css/flowdetail.less b/web/src/css/flowdetail.less index 453cf425..9feb7245 100644 --- a/web/src/css/flowdetail.less +++ b/web/src/css/flowdetail.less @@ -5,7 +5,8 @@ .flow-detail { width: 100%; - overflow: auto; + overflow-x: auto; + overflow-y: scroll; nav { background-color: #F2F2F2; diff --git a/web/src/js/components/flowview/contentview.js b/web/src/js/components/flowview/contentview.js index 09a64bb2..30a40faa 100644 --- a/web/src/js/components/flowview/contentview.js +++ b/web/src/js/components/flowview/contentview.js @@ -12,23 +12,56 @@ var Image = React.createClass({ } }, render: function () { - var message_name = this.props.flow.request === this.props.message ? "request" : "response"; - var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + var url = MessageUtils.getContentURL(this.props.flow, this.props.message); return
preview
; } }); +var RawMixin = { + getInitialState: function () { + return { + content: undefined + } + }, + requestContent: function (nextProps) { + this.setState({content: undefined}); + var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + request.done(function (data) { + this.setState({content: data}); + }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { + this.setState({content: "AJAX Error: " + textStatus}); + }.bind(this)); + + }, + componentWillMount: function () { + this.requestContent(this.props); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps); + } + }, + render: function () { + if (!this.state.content) { + return
+ +
; + } + return this.renderContent(); + } +}; + var Raw = React.createClass({ + mixins: [RawMixin], statics: { matches: function (message) { return true; } }, - render: function () { - //FIXME - return
raw
; + renderContent: function () { + return
{this.state.content}
; } }); diff --git a/web/src/js/flow/utils.js b/web/src/js/flow/utils.js index dd7f763b..a67db94f 100644 --- a/web/src/js/flow/utils.js +++ b/web/src/js/flow/utils.js @@ -1,4 +1,5 @@ var _ = require("lodash"); +var $ = require("jquery"); var MessageUtils = { getContentType: function (message) { @@ -34,6 +35,18 @@ var MessageUtils = { } } return false; + }, + getContentURL: function(flow, message){ + if(message === flow.request){ + message = "request"; + } else if (message === flow.response){ + message = "response"; + } + return "/flows/" + flow.id + "/" + message + "/content"; + }, + getContent: function(flow, message){ + var url = MessageUtils.getContentURL(flow, message); + return $.get(url); } }; -- cgit v1.2.3 From c9a09754464e27a5f34295d8a1c0b435248c104c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 15:11:54 +1300 Subject: console: observe state objects for changes, fire event to update status bar. --- libmproxy/console/__init__.py | 9 +++++++++ libmproxy/console/signals.py | 3 +++ libmproxy/console/statusbar.py | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index 34abe6f4..b593d282 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -33,6 +33,10 @@ class ConsoleState(flow.State): self.flowsettings = weakref.WeakKeyDictionary() + def __setattr__(self, name, value): + self.__dict__[name] = value + signals.update_settings.send(self) + def add_flow_setting(self, flow, key, value): d = self.flowsettings.setdefault(flow, {}) d[key] = value @@ -212,6 +216,10 @@ class ConsoleMaster(flow.FlowMaster): self.start_app(self.options.app_host, self.options.app_port) signals.call_in.connect(self.sig_call_in) + def __setattr__(self, name, value): + self.__dict__[name] = value + signals.update_settings.send(self) + def sig_call_in(self, sender, seconds, callback, args=()): def cb(*_): return callback(*args) @@ -598,6 +606,7 @@ class ConsoleMaster(flow.FlowMaster): elif a == "u": self.server.config.no_upstream_cert =\ not self.server.config.no_upstream_cert + signals.update_settings.send(self) def shutdown(self): self.state.killall(self) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index e8944afb..a62b2a4e 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -17,3 +17,6 @@ call_in = blinker.Signal() # Focus the body, footer or header of the main window focus = blinker.Signal() + +# Fired when settings change +update_settings = blinker.Signal() diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 30819188..7663ee44 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -113,6 +113,10 @@ class StatusBar(urwid.WidgetWrap): self.ab = ActionBar() self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) + signals.update_settings.connect(self.sig_update_settings) + + def sig_update_settings(self, sender): + self.redraw() def keypress(self, *args, **kwargs): return self.ab.keypress(*args, **kwargs) -- cgit v1.2.3 From aa9a38522f5fbfef556578b6018ad365ad5e844d Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 15:58:32 +1300 Subject: Remove refresh_flow mechanism in favor of a signal-based implementation --- libmproxy/console/__init__.py | 21 +++++---------- libmproxy/console/flowview.py | 60 ++++++++++++++++++++++++------------------- libmproxy/console/signals.py | 3 +++ 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index b593d282..d988ba84 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -263,7 +263,7 @@ class ConsoleMaster(flow.FlowMaster): if f.error: self._run_script_method("error", s, f) s.unload() - self.refresh_flow(f) + signals.flow_change.send(self, flow = f) def set_script(self, command): if not command: @@ -378,7 +378,7 @@ class ConsoleMaster(flow.FlowMaster): changed = self.tick(self.masterq, timeout=0) if changed: self.loop.draw_screen() - self.statusbar.redraw() + signals.update_settings.send() self.loop.set_alarm_in(0.01, self.ticker) def run(self): @@ -397,7 +397,6 @@ class ConsoleMaster(flow.FlowMaster): screen = self.ui, ) self.view_flowlist() - self.statusbar.redraw() self.server.start_slave( controller.Slave, @@ -446,7 +445,6 @@ class ConsoleMaster(flow.FlowMaster): header = self.header, footer = self.statusbar ) - self.statusbar.redraw() return self.view def view_help(self): @@ -633,15 +631,10 @@ class ConsoleMaster(flow.FlowMaster): def refresh_focus(self): if self.state.view: - self.refresh_flow(self.state.view[self.state.focus]) - - def refresh_flow(self, c): - if hasattr(self.header, "refresh_flow"): - self.header.refresh_flow(c) - if hasattr(self.body, "refresh_flow"): - self.body.refresh_flow(c) - if hasattr(self.statusbar, "refresh_flow"): - self.statusbar.refresh_flow(c) + signals.flow_change.send( + self, + flow = self.state.view[self.state.focus] + ) def process_flow(self, f): if self.state.intercept and f.match(self.state.intercept) and not f.request.is_replay: @@ -649,7 +642,7 @@ class ConsoleMaster(flow.FlowMaster): else: f.reply() self.sync_list_view() - self.refresh_flow(f) + signals.flow_change.send(self, flow = f) def clear_events(self): self.eventlist[:] = [] diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index d63b8a8c..2dd2cb82 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -88,10 +88,17 @@ class FlowViewHeader(urwid.WidgetWrap): def __init__(self, master, f): self.master, self.flow = master, f self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) - - def refresh_flow(self, f): - if f == self.flow: - self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) + signals.flow_change.connect(self.sig_flow_change) + + def sig_flow_change(self, sender, flow): + if flow == self.flow: + self._w = common.format_flow( + flow, + False, + extended=True, + padding=0, + hostheader=self.master.showhost + ) class CallbackCache: @@ -119,6 +126,14 @@ class FlowView(urwid.WidgetWrap): self.view_response() else: self.view_request() + signals.flow_change.connect(self.sig_flow_change) + + def sig_flow_change(self, sender, flow): + if flow == self.flow: + if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response: + self.view_response() + else: + self.view_request() def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event, is_request) @@ -332,7 +347,7 @@ class FlowView(urwid.WidgetWrap): list_box = urwid.ListBox(merged) list_box.set_focus(focus_position + 2) self._w = self.wrap_body(const, list_box) - self.master.statusbar.redraw() + signals.update_settings.send(self) self.last_displayed_body = list_box @@ -456,7 +471,6 @@ class FlowView(urwid.WidgetWrap): self.state.view_flow_mode = common.VIEW_FLOW_REQUEST body = self.conn_text(self.flow.request) self._w = self.wrap_body(common.VIEW_FLOW_REQUEST, body) - self.master.statusbar.redraw() def view_response(self): self.state.view_flow_mode = common.VIEW_FLOW_RESPONSE @@ -476,19 +490,11 @@ class FlowView(urwid.WidgetWrap): ] ) self._w = self.wrap_body(common.VIEW_FLOW_RESPONSE, body) - self.master.statusbar.redraw() - - def refresh_flow(self, c=None): - if c == self.flow: - if self.state.view_flow_mode == common.VIEW_FLOW_RESPONSE and self.flow.response: - self.view_response() - else: - self.view_request() def set_method_raw(self, m): if m: self.flow.request.method = m - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def edit_method(self, m): if m == "e": @@ -501,7 +507,7 @@ class FlowView(urwid.WidgetWrap): for i in common.METHOD_OPTIONS: if i[1] == m: self.flow.request.method = i[0].upper() - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_url(self, url): request = self.flow.request @@ -509,7 +515,7 @@ class FlowView(urwid.WidgetWrap): request.url = str(url) except ValueError: return "Invalid URL." - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_resp_code(self, code): response = self.flow.response @@ -520,12 +526,12 @@ class FlowView(urwid.WidgetWrap): import BaseHTTPServer if BaseHTTPServer.BaseHTTPRequestHandler.responses.has_key(int(code)): response.msg = BaseHTTPServer.BaseHTTPRequestHandler.responses[int(code)][0] - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_resp_msg(self, msg): response = self.flow.response response.msg = msg - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def set_headers(self, lst, conn): conn.headers = flow.ODictCaseless(lst) @@ -614,7 +620,7 @@ class FlowView(urwid.WidgetWrap): text = message.msg, callback = self.set_resp_msg ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def _view_nextprev_flow(self, np, flow): try: @@ -642,7 +648,7 @@ class FlowView(urwid.WidgetWrap): (self.state.view_flow_mode, "prettyview"), contentview.get_by_shortcut(t) ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def delete_body(self, t): if t == "m": @@ -653,7 +659,7 @@ class FlowView(urwid.WidgetWrap): self.flow.request.content = val else: self.flow.response.content = val - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) def keypress(self, size, key): if key == " ": @@ -736,7 +742,7 @@ class FlowView(urwid.WidgetWrap): (self.state.view_flow_mode, "fullcontents"), True ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="") elif key == "g": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: @@ -760,13 +766,13 @@ class FlowView(urwid.WidgetWrap): r = self.master.replay_request(self.flow) if r: signals.status_message.send(message=r) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) elif key == "V": if not self.flow.modified(): signals.status_message.send(message="Flow not modified.") return self.state.revert(self.flow) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) signals.status_message.send(message="Reverted.") elif key == "W": signals.status_prompt_path.send( @@ -817,7 +823,7 @@ class FlowView(urwid.WidgetWrap): callback = self.encode_callback, args = (conn,) ) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) elif key == "/": last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" @@ -839,4 +845,4 @@ class FlowView(urwid.WidgetWrap): "d": "deflate", } conn.encode(encoding_map[key]) - self.master.refresh_flow(self.flow) + signals.flow_change.send(self, flow = self.flow) diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index a62b2a4e..9afde6f4 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -20,3 +20,6 @@ focus = blinker.Signal() # Fired when settings change update_settings = blinker.Signal() + +# Fired when a flow changes +flow_change = blinker.Signal() -- cgit v1.2.3 From 120c8db8a413018bde60d156f480ade001b492ef Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 16:59:11 +1300 Subject: console: refactor the way we keep global view state --- libmproxy/console/__init__.py | 99 ++++++++++++++++++------------------- libmproxy/console/flowdetailview.py | 5 +- libmproxy/console/grideditor.py | 10 ++-- libmproxy/console/help.py | 5 +- libmproxy/console/statusbar.py | 7 ++- 5 files changed, 60 insertions(+), 66 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index d988ba84..f6f8e721 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -188,8 +188,6 @@ class ConsoleMaster(flow.FlowMaster): self.eventlog = options.eventlog self.eventlist = urwid.SimpleListWalker([]) - self.statusbar = None - if options.client_replay: self.client_playback_path(options.client_replay) @@ -287,12 +285,7 @@ class ConsoleMaster(flow.FlowMaster): try: return flow.read_flows_from_paths([path]) except flow.FlowReadError as e: - if not self.statusbar: - print >> sys.stderr, e.strerror - sys.exit(1) - else: - signals.status_message.send(message=e.strerror) - return None + signals.status_message.send(message=e.strerror) def client_playback_path(self, path): flows = self._readflows(path) @@ -326,7 +319,9 @@ class ConsoleMaster(flow.FlowMaster): try: subprocess.call(cmd) except: - signals.status_message.send(message="Can't start editor: %s" % " ".join(c)) + signals.status_message.send( + message = "Can't start editor: %s" % " ".join(c) + ) else: data = open(name, "rb").read() self.ui.start() @@ -386,17 +381,11 @@ class ConsoleMaster(flow.FlowMaster): self.ui.set_terminal_properties(256) self.ui.register_palette(self.palette.palette()) self.flow_list_walker = flowlist.FlowListWalker(self, self.state) - self.view = None - self.statusbar = None - self.header = None - self.body = None self.help_context = None - self.onekey = False self.loop = urwid.MainLoop( - self.view, + urwid.SolidFill("x"), screen = self.ui, ) - self.view_flowlist() self.server.start_slave( controller.Slave, @@ -425,6 +414,11 @@ class ConsoleMaster(flow.FlowMaster): raise urwid.ExitMainLoop signal.signal(signal.SIGINT, exit) + self.loop.set_alarm_in( + 0.0001, + lambda *args: self.view_flowlist() + ) + try: self.loop.run() except Exception: @@ -438,43 +432,38 @@ class ConsoleMaster(flow.FlowMaster): sys.stderr.flush() self.shutdown() - def make_view(self): - self.view = window.Window( - self, - self.body, - header = self.header, - footer = self.statusbar - ) - return self.view - def view_help(self): - h = help.HelpView( + self.loop.widget = window.Window( self, - self.help_context, - (self.statusbar, self.body, self.header) + help.HelpView( + self, + self.help_context, + self.loop.widget, + ), + None, + statusbar.StatusBar(self, help.footer) ) - self.statusbar = statusbar.StatusBar(self, help.footer) - self.body = h - self.header = None - self.loop.widget = self.make_view() def view_flowdetails(self, flow): - h = flowdetailview.FlowDetailsView( + self.loop.widget = window.Window( self, - flow, - (self.statusbar, self.body, self.header) + flowdetailview.FlowDetailsView( + self, + flow, + self.loop.widget + ), + None, + statusbar.StatusBar(self, flowdetailview.footer) ) - self.statusbar = statusbar.StatusBar(self, flowdetailview.footer) - self.body = h - self.header = None - self.loop.widget = self.make_view() def view_grideditor(self, ge): - self.body = ge - self.header = None self.help_context = ge.make_help() - self.statusbar = statusbar.StatusBar(self, grideditor.footer) - self.loop.widget = self.make_view() + self.loop.widget = window.Window( + self, + ge, + None, + statusbar.StatusBar(self, grideditor.FOOTER) + ) def view_flowlist(self): if self.ui.started: @@ -483,24 +472,30 @@ class ConsoleMaster(flow.FlowMaster): self.state.set_focus(self.state.flow_count()) if self.eventlog: - self.body = flowlist.BodyPile(self) + body = flowlist.BodyPile(self) else: - self.body = flowlist.FlowListBox(self) - self.statusbar = statusbar.StatusBar(self, flowlist.footer) - self.header = None + body = flowlist.FlowListBox(self) self.state.view_mode = common.VIEW_LIST - self.loop.widget = self.make_view() self.help_context = flowlist.help_context + self.loop.widget = window.Window( + self, + body, + None, + statusbar.StatusBar(self, flowlist.footer) + ) + self.loop.draw_screen() def view_flow(self, flow): - self.body = flowview.FlowView(self, self.state, flow) - self.header = flowview.FlowViewHeader(self, flow) - self.statusbar = statusbar.StatusBar(self, flowview.footer) self.state.set_focus_flow(flow) self.state.view_mode = common.VIEW_FLOW - self.loop.widget = self.make_view() self.help_context = flowview.help_context + self.loop.widget = window.Window( + self, + flowview.FlowView(self, self.state, flow), + flowview.FlowViewHeader(self, flow), + statusbar.StatusBar(self, flowview.footer) + ) def _write_flows(self, path, flows): if not path: diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index f351bff1..15350ea1 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -18,10 +18,7 @@ class FlowDetailsView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.statusbar = self.state[0] - self.master.body = self.state[1] - self.master.header = self.state[2] - self.master.loop.widget = self.master.make_view() + self.master.loop.widget = self.state return None elif key == "?": key = None diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index dc3bad0e..a1d662c8 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -10,11 +10,11 @@ from .. import utils, filt, script from netlib import http_uastrings -footer = [ +FOOTER = [ ('heading_key', "enter"), ":edit ", ('heading_key', "q"), ":back ", ] -footer_editing = [ +FOOTER_EDITING = [ ('heading_key', "esc"), ":stop editing ", ] @@ -164,12 +164,12 @@ class GridWalker(urwid.ListWalker): self.editing = GridRow( self.focus_col, True, self.editor, self.lst[self.focus] ) - self.editor.master.statusbar.update(footer_editing) + self.editor.master.loop.widget.footer.update(FOOTER_EDITING) self._modified() def stop_edit(self): if self.editing: - self.editor.master.statusbar.update(footer) + self.editor.master.loop.widget.footer.update(FOOTER) self.set_current_value(self.editing.get_edit_value(), False) self.editing = False self._modified() @@ -268,7 +268,7 @@ class GridEditor(urwid.WidgetWrap): self.lb, header = urwid.Pile([title, h]) ) - self.master.statusbar.update("") + self.master.loop.widget.footer.update("") self.show_empty_msg() def show_empty_msg(self): diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 6bb49a92..109a9792 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -180,10 +180,7 @@ class HelpView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.statusbar = self.state[0] - self.master.body = self.state[1] - self.master.header = self.state[2] - self.master.loop.widget = self.master.make_view() + self.master.loop.widget = self.state return None elif key == "?": key = None diff --git a/libmproxy/console/statusbar.py b/libmproxy/console/statusbar.py index 7663ee44..7fb15aa6 100644 --- a/libmproxy/console/statusbar.py +++ b/libmproxy/console/statusbar.py @@ -114,6 +114,7 @@ class StatusBar(urwid.WidgetWrap): self.ib = urwid.WidgetWrap(urwid.Text("")) self._w = urwid.Pile([self.ib, self.ab]) signals.update_settings.connect(self.sig_update_settings) + self.redraw() def sig_update_settings(self, sender): self.redraw() @@ -188,7 +189,11 @@ class StatusBar(urwid.WidgetWrap): if self.master.state.follow_focus: opts.append("following") if self.master.stream_large_bodies: - opts.append("stream:%s" % utils.pretty_size(self.master.stream_large_bodies.max_size)) + opts.append( + "stream:%s" % utils.pretty_size( + self.master.stream_large_bodies.max_size + ) + ) if opts: r.append("[%s]"%(":".join(opts))) -- cgit v1.2.3 From 08bb07653306ed0f84932391732391227ee07ba2 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:18:53 +1300 Subject: console: signal-based view stack, unifying mechanisms for help, flow views, etc. --- libmproxy/console/__init__.py | 36 +++++++++++++++++------------------- libmproxy/console/common.py | 3 --- libmproxy/console/flowdetailview.py | 8 ++++---- libmproxy/console/flowview.py | 12 +++++------- libmproxy/console/grideditor.py | 2 +- libmproxy/console/help.py | 7 +++---- libmproxy/console/signals.py | 5 +++++ 7 files changed, 35 insertions(+), 38 deletions(-) diff --git a/libmproxy/console/__init__.py b/libmproxy/console/__init__.py index f6f8e721..90c8bd89 100644 --- a/libmproxy/console/__init__.py +++ b/libmproxy/console/__init__.py @@ -28,7 +28,6 @@ class ConsoleState(flow.State): self.follow_focus = None self.default_body_view = contentview.get("Auto") - self.view_mode = common.VIEW_LIST self.view_flow_mode = common.VIEW_FLOW_REQUEST self.flowsettings = weakref.WeakKeyDictionary() @@ -210,9 +209,13 @@ class ConsoleMaster(flow.FlowMaster): print >> sys.stderr, "Stream file error:", err sys.exit(1) + self.view_stack = [] + if options.app: self.start_app(self.options.app_host, self.options.app_port) signals.call_in.connect(self.sig_call_in) + signals.pop_view_state.connect(self.sig_pop_view_state) + signals.push_view_state.connect(self.sig_push_view_state) def __setattr__(self, name, value): self.__dict__[name] = value @@ -223,6 +226,13 @@ class ConsoleMaster(flow.FlowMaster): return callback(*args) self.loop.set_alarm_in(seconds, cb) + def sig_pop_view_state(self, sender): + if self.view_stack: + self.loop.widget = self.view_stack.pop() + + def sig_push_view_state(self, sender): + self.view_stack.append(self.loop.widget) + def start_stream_to_path(self, path, mode="wb"): path = os.path.expanduser(path) try: @@ -433,30 +443,25 @@ class ConsoleMaster(flow.FlowMaster): self.shutdown() def view_help(self): + signals.push_view_state.send(self) self.loop.widget = window.Window( self, - help.HelpView( - self, - self.help_context, - self.loop.widget, - ), + help.HelpView(self.help_context), None, statusbar.StatusBar(self, help.footer) ) def view_flowdetails(self, flow): + signals.push_view_state.send(self) self.loop.widget = window.Window( self, - flowdetailview.FlowDetailsView( - self, - flow, - self.loop.widget - ), + flowdetailview.FlowDetailsView(low), None, statusbar.StatusBar(self, flowdetailview.footer) ) def view_grideditor(self, ge): + signals.push_view_state.send(self) self.help_context = ge.make_help() self.loop.widget = window.Window( self, @@ -475,7 +480,6 @@ class ConsoleMaster(flow.FlowMaster): body = flowlist.BodyPile(self) else: body = flowlist.FlowListBox(self) - self.state.view_mode = common.VIEW_LIST self.help_context = flowlist.help_context self.loop.widget = window.Window( @@ -487,8 +491,8 @@ class ConsoleMaster(flow.FlowMaster): self.loop.draw_screen() def view_flow(self, flow): + signals.push_view_state.send(self) self.state.set_focus_flow(flow) - self.state.view_mode = common.VIEW_FLOW self.help_context = flowview.help_context self.loop.widget = window.Window( self, @@ -548,12 +552,6 @@ class ConsoleMaster(flow.FlowMaster): self.state.default_body_view = v self.refresh_focus() - def pop_view(self): - if self.state.view_mode == common.VIEW_FLOW: - self.view_flow(self.state.view[self.state.focus]) - else: - self.view_flowlist() - def edit_scripts(self, scripts): commands = [x[0] for x in scripts] # remove outer array if commands == [s.command for s in self.scripts]: diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index c0593af4..a0590bb1 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -13,9 +13,6 @@ try: except: pyperclip = False -VIEW_LIST = 0 -VIEW_FLOW = 1 - VIEW_FLOW_REQUEST = 0 VIEW_FLOW_RESPONSE = 1 diff --git a/libmproxy/console/flowdetailview.py b/libmproxy/console/flowdetailview.py index 15350ea1..8bfdae4a 100644 --- a/libmproxy/console/flowdetailview.py +++ b/libmproxy/console/flowdetailview.py @@ -1,6 +1,6 @@ from __future__ import absolute_import import urwid -from . import common +from . import common, signals from .. import utils footer = [ @@ -8,8 +8,8 @@ footer = [ ] class FlowDetailsView(urwid.ListBox): - def __init__(self, master, flow, state): - self.master, self.flow, self.state = master, flow, state + def __init__(self, flow): + self.flow = flow urwid.ListBox.__init__( self, self.flowtext() @@ -18,7 +18,7 @@ class FlowDetailsView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.loop.widget = self.state + signals.pop_view_state.send(self) return None elif key == "?": key = None diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 2dd2cb82..fcb967cc 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -114,9 +114,6 @@ cache = CallbackCache() class FlowView(urwid.WidgetWrap): - REQ = 0 - RESP = 1 - highlight_color = "focusfield" def __init__(self, master, state, flow): @@ -633,8 +630,9 @@ class FlowView(urwid.WidgetWrap): new_flow, new_idx = self.state.get_prev(idx) if new_flow is None: signals.status_message.send(message="No more flows!") - return - self.master.view_flow(new_flow) + else: + signals.pop_view_state.send(self) + self.master.view_flow(new_flow) def view_next_flow(self, flow): return self._view_nextprev_flow("next", flow) @@ -673,8 +671,8 @@ class FlowView(urwid.WidgetWrap): conn = self.flow.response if key == "q": - self.master.view_flowlist() - key = None + signals.pop_view_state.send(self) + return None elif key == "tab": if self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: self.view_response() diff --git a/libmproxy/console/grideditor.py b/libmproxy/console/grideditor.py index a1d662c8..4bcc0171 100644 --- a/libmproxy/console/grideditor.py +++ b/libmproxy/console/grideditor.py @@ -323,7 +323,7 @@ class GridEditor(urwid.WidgetWrap): if not i[1] and any([x.strip() for x in i[0]]): res.append(i[0]) self.callback(res, *self.cb_args, **self.cb_kwargs) - self.master.pop_view() + signals.pop_view_state.send(self) elif key in ["h", "left"]: self.walker.left() elif key in ["l", "right"]: diff --git a/libmproxy/console/help.py b/libmproxy/console/help.py index 109a9792..73cd8a50 100644 --- a/libmproxy/console/help.py +++ b/libmproxy/console/help.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import urwid -from . import common +from . import common, signals from .. import filt, version footer = [ @@ -12,8 +12,7 @@ footer = [ class HelpView(urwid.ListBox): - def __init__(self, master, help_context, state): - self.master, self.state = master, state + def __init__(self, help_context): self.help_context = help_context or [] urwid.ListBox.__init__( self, @@ -180,7 +179,7 @@ class HelpView(urwid.ListBox): def keypress(self, size, key): key = common.shortcuts(key) if key == "q": - self.master.loop.widget = self.state + signals.pop_view_state.send(self) return None elif key == "?": key = None diff --git a/libmproxy/console/signals.py b/libmproxy/console/signals.py index 9afde6f4..e4c11f5a 100644 --- a/libmproxy/console/signals.py +++ b/libmproxy/console/signals.py @@ -23,3 +23,8 @@ update_settings = blinker.Signal() # Fired when a flow changes flow_change = blinker.Signal() + + +# Pop and push view state onto a stack +pop_view_state = blinker.Signal() +push_view_state = blinker.Signal() -- cgit v1.2.3 From a1c21d9774a1b0e77bcb8116cad20973b2561c28 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:23:27 +1300 Subject: Fix unit tests --- test/test_console_help.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_console_help.py b/test/test_console_help.py index a410bd2e..24517439 100644 --- a/test/test_console_help.py +++ b/test/test_console_help.py @@ -19,12 +19,12 @@ class DummyMaster: class TestHelp: def test_helptext(self): - h = help.HelpView(None, "foo", None) + h = help.HelpView(None) assert h.helptext() def test_keypress(self): master = DummyMaster() - h = help.HelpView(master, "foo", [1, 2, 3]) + h = help.HelpView([1, 2, 3]) assert not h.keypress((0, 0), "q") assert not h.keypress((0, 0), "?") assert h.keypress((0, 0), "o") == "o" -- cgit v1.2.3 From 15f65d63f633b6b6a540f74006efe542796aa7e4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:28:13 +1300 Subject: Trigger flow change when flow elements are edited --- libmproxy/console/flowview.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index fcb967cc..04440888 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -532,19 +532,28 @@ class FlowView(urwid.WidgetWrap): def set_headers(self, lst, conn): conn.headers = flow.ODictCaseless(lst) + signals.flow_change.send(self, flow = self.flow) def set_query(self, lst, conn): conn.set_query(flow.ODict(lst)) + signals.flow_change.send(self, flow = self.flow) def set_path_components(self, lst, conn): conn.set_path_components([i[0] for i in lst]) + signals.flow_change.send(self, flow = self.flow) def set_form(self, lst, conn): conn.set_form_urlencoded(flow.ODict(lst)) + signals.flow_change.send(self, flow = self.flow) def edit_form(self, conn): self.master.view_grideditor( - grideditor.URLEncodedFormEditor(self.master, conn.get_form_urlencoded().lst, self.set_form, conn) + grideditor.URLEncodedFormEditor( + self.master, + conn.get_form_urlencoded().lst, + self.set_form, + conn + ) ) def edit_form_confirm(self, key, conn): @@ -586,7 +595,14 @@ class FlowView(urwid.WidgetWrap): else: self.edit_form(message) elif part == "h": - self.master.view_grideditor(grideditor.HeaderEditor(self.master, message.headers.lst, self.set_headers, message)) + self.master.view_grideditor( + grideditor.HeaderEditor( + self.master, + message.headers.lst, + self.set_headers, + message + ) + ) elif part == "p": p = message.get_path_components() p = [[i] for i in p] -- cgit v1.2.3 From a2da38cc8339887abef4efa23cc54fa02c981f3f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 17:33:25 +1300 Subject: Whitespace, indentation, formatting --- libmproxy/console/flowview.py | 128 +++++++++++++++++++++++++++++++++--------- 1 file changed, 102 insertions(+), 26 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 04440888..e864cf47 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -87,7 +87,13 @@ footer = [ class FlowViewHeader(urwid.WidgetWrap): def __init__(self, master, f): self.master, self.flow = master, f - self._w = common.format_flow(f, False, extended=True, padding=0, hostheader=self.master.showhost) + self._w = common.format_flow( + f, + False, + extended=True, + padding=0, + hostheader=self.master.showhost + ) signals.flow_change.connect(self.sig_flow_change) def sig_flow_change(self, sender, flow): @@ -133,7 +139,14 @@ class FlowView(urwid.WidgetWrap): self.view_request() def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): - return contentview.get_content_view(viewmode, hdrItems, content, limit, self.master.add_event, is_request) + return contentview.get_content_view( + viewmode, + hdrItems, + content, + limit, + self.master.add_event, + is_request + ) def content_view(self, viewmode, conn): full = self.state.get_flow_setting( @@ -219,7 +232,8 @@ class FlowView(urwid.WidgetWrap): def conn_text(self, conn): """ - Same as conn_text_raw, but returns result wrapped in a listbox ready for usage. + Same as conn_text_raw, but returns result wrapped in a listbox ready for + usage. """ headers, msg, body = self.conn_text_raw(conn) merged = self.conn_text_merge(headers, msg, body) @@ -290,7 +304,9 @@ class FlowView(urwid.WidgetWrap): """ runs the previous search again, forwards or backwards. """ - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, "last_search_string" + ) if last_search_string: message = self.search(last_search_string, backwards) if message: @@ -331,7 +347,11 @@ class FlowView(urwid.WidgetWrap): # generate the body, highlight the words and get focus headers, msg, body = self.conn_text_raw(text) try: - body, focus_position = self.search_highlight_text(body, search_string, backwards=backwards) + body, focus_position = self.search_highlight_text( + body, + search_string, + backwards=backwards + ) except SearchError: return "Search not supported in this view." @@ -348,7 +368,11 @@ class FlowView(urwid.WidgetWrap): self.last_displayed_body = list_box - wrapped, wrapped_message = self.search_wrapped_around(last_find_line, last_search_index, backwards) + wrapped, wrapped_message = self.search_wrapped_around( + last_find_line, + last_search_index, + backwards + ) if wrapped: return wrapped_message @@ -356,9 +380,15 @@ class FlowView(urwid.WidgetWrap): def search_get_start(self, search_string): start_line = 0 start_index = 0 - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, + "last_search_string" + ) if search_string == last_search_string: - start_line = self.state.get_flow_setting(self.flow, "last_find_line") + start_line = self.state.get_flow_setting( + self.flow, + "last_find_line" + ) start_index = self.state.get_flow_setting(self.flow, "last_search_index") @@ -403,7 +433,10 @@ class FlowView(urwid.WidgetWrap): found = False text_objects = copy.deepcopy(text_objects) - loop_range = self.search_get_range(len(text_objects), start_line, backwards) + loop_range = self.search_get_range( + len(text_objects), + start_line, backwards + ) for i in loop_range: text_object = text_objects[i] @@ -415,10 +448,19 @@ class FlowView(urwid.WidgetWrap): if i != start_line: start_index = 0 - find_index = self.search_find(text, search_string, start_index, backwards) + find_index = self.search_find( + text, + search_string, + start_index, + backwards + ) if find_index != -1: - new_text = self.search_highlight_object(text, find_index, search_string) + new_text = self.search_highlight_object( + text, + find_index, + search_string + ) text_objects[i] = new_text found = True @@ -436,14 +478,26 @@ class FlowView(urwid.WidgetWrap): focus_pos = None else: if not backwards: - self.state.add_flow_setting(self.flow, "last_search_index", 0) - self.state.add_flow_setting(self.flow, "last_find_line", 0) + self.state.add_flow_setting( + self.flow, "last_search_index", 0 + ) + self.state.add_flow_setting( + self.flow, "last_find_line", 0 + ) else: - self.state.add_flow_setting(self.flow, "last_search_index", None) - self.state.add_flow_setting(self.flow, "last_find_line", len(text_objects) - 1) + self.state.add_flow_setting( + self.flow, "last_search_index", None + ) + self.state.add_flow_setting( + self.flow, "last_find_line", len(text_objects) - 1 + ) - text_objects, focus_pos = self.search_highlight_text(text_objects, - search_string, looping=True, backwards=backwards) + text_objects, focus_pos = self.search_highlight_text( + text_objects, + search_string, + looping=True, + backwards=backwards + ) return text_objects, focus_pos @@ -575,10 +629,12 @@ class FlowView(urwid.WidgetWrap): self.flow.backup() if part == "r": with decoded(message): - # Fix an issue caused by some editors when editing a request/response body. - # Many editors make it hard to save a file without a terminating newline on the last - # line. When editing message bodies, this can cause problems. For now, I just - # strip the newlines off the end of the body when we return from an editor. + # Fix an issue caused by some editors when editing a + # request/response body. Many editors make it hard to save a + # file without a terminating newline on the last line. When + # editing message bodies, this can cause problems. For now, I + # just strip the newlines off the end of the body when we return + # from an editor. c = self.master.spawn_editor(message.content or "") message.content = c.rstrip("\n") elif part == "f": @@ -606,9 +662,22 @@ class FlowView(urwid.WidgetWrap): elif part == "p": p = message.get_path_components() p = [[i] for i in p] - self.master.view_grideditor(grideditor.PathEditor(self.master, p, self.set_path_components, message)) + self.master.view_grideditor( + grideditor.PathEditor( + self.master, + p, + self.set_path_components, + message + ) + ) elif part == "q": - self.master.view_grideditor(grideditor.QueryEditor(self.master, message.get_query().lst, self.set_query, message)) + self.master.view_grideditor( + grideditor.QueryEditor( + self.master, + message.get_query().lst, + self.set_query, message + ) + ) elif part == "u" and self.state.view_flow_mode == common.VIEW_FLOW_REQUEST: signals.status_prompt.send( prompt = "URL", @@ -801,7 +870,9 @@ class FlowView(urwid.WidgetWrap): if os.environ.has_key("EDITOR") or os.environ.has_key("PAGER"): self.master.spawn_external_viewer(conn.content, t) else: - signals.status_message.send(message="Error! Set $EDITOR or $PAGER.") + signals.status_message.send( + message = "Error! Set $EDITOR or $PAGER." + ) elif key == "|": signals.status_prompt_path.send( prompt = "Send flow to script", @@ -826,7 +897,9 @@ class FlowView(urwid.WidgetWrap): e = conn.headers.get_first("content-encoding", "identity") if e != "identity": if not conn.decode(): - signals.status_message.send(message="Could not decode - invalid data?") + signals.status_message.send( + message = "Could not decode - invalid data?" + ) else: signals.status_prompt_onekey.send( prompt = "Select encoding: ", @@ -839,7 +912,10 @@ class FlowView(urwid.WidgetWrap): ) signals.flow_change.send(self, flow = self.flow) elif key == "/": - last_search_string = self.state.get_flow_setting(self.flow, "last_search_string") + last_search_string = self.state.get_flow_setting( + self.flow, + "last_search_string" + ) search_prompt = "Search body ["+last_search_string+"]" if last_search_string else "Search body" signals.status_prompt.send( prompt = search_prompt, -- cgit v1.2.3 From 842e23d3e386169d9a90cef2a634c55a3e5fdd8e Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 21:00:41 +1300 Subject: Replace far-too-clever decorator LRU cache with something simpler --- libmproxy/console/common.py | 9 +++----- libmproxy/console/flowview.py | 15 +++--------- libmproxy/utils.py | 53 +++++++++++++++++++------------------------ test/test_utils.py | 26 ++++++++++----------- test/tools/testpatt | 10 ++++---- 5 files changed, 47 insertions(+), 66 deletions(-) diff --git a/libmproxy/console/common.py b/libmproxy/console/common.py index a0590bb1..2f143f01 100644 --- a/libmproxy/console/common.py +++ b/libmproxy/console/common.py @@ -327,11 +327,7 @@ def ask_save_body(part, master, state, flow): signals.status_message.send(message="No content to save.") -class FlowCache: - @utils.LRUCache(200) - def format_flow(self, *args): - return raw_format_flow(*args) -flowcache = FlowCache() +flowcache = utils.LRUCache(800) def format_flow(f, focus, extended=False, hostheader=False, padding=2): @@ -370,6 +366,7 @@ def format_flow(f, focus, extended=False, hostheader=False, padding=2): d["resp_ctype"] = t[0].split(";")[0] else: d["resp_ctype"] = "" - return flowcache.format_flow( + return flowcache.get( + raw_format_flow, tuple(sorted(d.items())), focus, extended, padding ) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index e864cf47..2c847fba 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -107,16 +107,7 @@ class FlowViewHeader(urwid.WidgetWrap): ) -class CallbackCache: - @utils.LRUCache(200) - def _callback(self, method, *args, **kwargs): - return getattr(self.obj, method)(*args, **kwargs) - - def callback(self, obj, method, *args, **kwargs): - # obj varies! - self.obj = obj - return self._callback(method, *args, **kwargs) -cache = CallbackCache() +cache = utils.LRUCache(200) class FlowView(urwid.WidgetWrap): @@ -158,8 +149,8 @@ class FlowView(urwid.WidgetWrap): limit = sys.maxint else: limit = contentview.VIEW_CUTOFF - description, text_objects = cache.callback( - self, "_cached_content_view", + description, text_objects = cache.get( + self._cached_content_view, viewmode, tuple(tuple(i) for i in conn.headers.lst), conn.content, diff --git a/libmproxy/utils.py b/libmproxy/utils.py index 51f2dc26..5ed70a45 100644 --- a/libmproxy/utils.py +++ b/libmproxy/utils.py @@ -119,40 +119,33 @@ pkg_data = Data(__name__) class LRUCache: """ - A decorator that implements a self-expiring LRU cache for class - methods (not functions!). - - Cache data is tracked as attributes on the object itself. There is - therefore a separate cache for each object instance. + A simple LRU cache for generated values. """ def __init__(self, size=100): self.size = size + self.cache = {} + self.cacheList = [] + + def get(self, gen, *args): + """ + gen: A (presumably expensive) generator function. The identity of + gen is NOT taken into account by the cache. + *args: A list of immutable arguments, used to establish identiy by + *the cache, and passed to gen to generate values. + """ + if self.cache.has_key(args): + self.cacheList.remove(args) + self.cacheList.insert(0, args) + return self.cache[args] + else: + ret = gen(*args) + self.cacheList.insert(0, args) + self.cache[args] = ret + if len(self.cacheList) > self.size: + d = self.cacheList.pop() + self.cache.pop(d) + return ret - def __call__(self, f): - cacheName = "_cached_%s"%f.__name__ - cacheListName = "_cachelist_%s"%f.__name__ - size = self.size - - @functools.wraps(f) - def wrap(self, *args): - if not hasattr(self, cacheName): - setattr(self, cacheName, {}) - setattr(self, cacheListName, []) - cache = getattr(self, cacheName) - cacheList = getattr(self, cacheListName) - if cache.has_key(args): - cacheList.remove(args) - cacheList.insert(0, args) - return cache[args] - else: - ret = f(self, *args) - cacheList.insert(0, args) - cache[args] = ret - if len(cacheList) > size: - d = cacheList.pop() - cache.pop(d) - return ret - return wrap def parse_content_type(c): """ diff --git a/test/test_utils.py b/test/test_utils.py index 78d1c072..1678a7de 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -62,38 +62,39 @@ def test_pretty_duration(): assert utils.pretty_duration(10) == "10.0s" assert utils.pretty_duration(100) == "100s" assert utils.pretty_duration(1000) == "1000s" - assert utils.pretty_duration(10000) == "10000s" + assert utils.pretty_duration(10000) == "10000s" assert utils.pretty_duration(1.123) == "1.12s" assert utils.pretty_duration(0.123) == "123ms" def test_LRUCache(): + cache = utils.LRUCache(2) class Foo: ran = False - @utils.LRUCache(2) - def one(self, x): + def gen(self, x): self.ran = True return x - f = Foo() - assert f.one(1) == 1 + + assert not f.ran + assert cache.get(f.gen, 1) == 1 assert f.ran f.ran = False - assert f.one(1) == 1 + assert cache.get(f.gen, 1) == 1 assert not f.ran f.ran = False - assert f.one(1) == 1 + assert cache.get(f.gen, 1) == 1 assert not f.ran - assert f.one(2) == 2 - assert f.one(3) == 3 + assert cache.get(f.gen, 2) == 2 + assert cache.get(f.gen, 3) == 3 assert f.ran f.ran = False - assert f.one(1) == 1 + assert cache.get(f.gen, 1) == 1 assert f.ran - assert len(f._cached_one) == 2 - assert len(f._cachelist_one) == 2 + assert len(cache.cacheList) == 2 + assert len(cache.cache) == 2 def test_unparse_url(): @@ -128,4 +129,3 @@ def test_safe_subn(): def test_urlencode(): assert utils.urlencode([('foo','bar')]) - diff --git a/test/tools/testpatt b/test/tools/testpatt index d4546d48..5ee1ea02 100755 --- a/test/tools/testpatt +++ b/test/tools/testpatt @@ -2,8 +2,8 @@ # Generate a test pattern with pathoc PATHOD=http://localhost:9999 -pathoc localhost:8080 "get:'$PATHOD/p/200:p0,1:b@200b':b@200b" -pathoc localhost:8080 "get:'$PATHOD/p/300:p0,1:b@200b':b@200b" -pathoc localhost:8080 "get:'$PATHOD/p/400:p0,1:b@200b':b@200b" -pathoc localhost:8080 "get:'$PATHOD/p/500:p0,1:b@200b':b@200b" -pathoc localhost:8080 "get:'$PATHOD/p/600:p0,1:b@200b':b@200b" +pathoc localhost:8080 "get:'$PATHOD/p/200:p0,1:b@2048b':b@2048b" +pathoc localhost:8080 "get:'$PATHOD/p/300:p0,1:b@2048b':b@2048b" +pathoc localhost:8080 "get:'$PATHOD/p/400:p0,1:b@2048b':b@2048b" +pathoc localhost:8080 "get:'$PATHOD/p/500:p0,1:b@2048b':b@2048b" +pathoc localhost:8080 "get:'$PATHOD/p/600:p0,1:b@2048b':b@2048b" -- cgit v1.2.3 From 6fb661dab518c036e9333d360f2efc91bc2631ab Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 22 Mar 2015 21:08:18 +1300 Subject: Unwind twisty maze of cache layers. Holy confusing, Batman. --- libmproxy/console/flowview.py | 57 +++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/libmproxy/console/flowview.py b/libmproxy/console/flowview.py index 2c847fba..1aebb0f0 100644 --- a/libmproxy/console/flowview.py +++ b/libmproxy/console/flowview.py @@ -129,43 +129,30 @@ class FlowView(urwid.WidgetWrap): else: self.view_request() - def _cached_content_view(self, viewmode, hdrItems, content, limit, is_request): - return contentview.get_content_view( - viewmode, - hdrItems, - content, - limit, - self.master.add_event, - is_request - ) - def content_view(self, viewmode, conn): - full = self.state.get_flow_setting( - self.flow, - (self.state.view_flow_mode, "fullcontents"), - False - ) - if full: - limit = sys.maxint + if conn.content == CONTENT_MISSING: + msg, body = "", [urwid.Text([("error", "[content missing]")])] + return (msg, body) else: - limit = contentview.VIEW_CUTOFF - description, text_objects = cache.get( - self._cached_content_view, - viewmode, - tuple(tuple(i) for i in conn.headers.lst), - conn.content, - limit, - isinstance(conn, HTTPRequest) - ) - return (description, text_objects) - - def cont_view_handle_missing(self, conn, viewmode): - if conn.content == CONTENT_MISSING: - msg, body = "", [urwid.Text([("error", "[content missing]")])] + full = self.state.get_flow_setting( + self.flow, + (self.state.view_flow_mode, "fullcontents"), + False + ) + if full: + limit = sys.maxint else: - msg, body = self.content_view(viewmode, conn) - - return (msg, body) + limit = contentview.VIEW_CUTOFF + description, text_objects = cache.get( + contentview.get_content_view, + viewmode, + tuple(tuple(i) for i in conn.headers.lst), + conn.content, + limit, + self.master.add_event, + isinstance(conn, HTTPRequest) + ) + return (description, text_objects) def viewmode_get(self, override): return self.state.default_body_view if override is None else override @@ -186,7 +173,7 @@ class FlowView(urwid.WidgetWrap): ) override = self.override_get() viewmode = self.viewmode_get(override) - msg, body = self.cont_view_handle_missing(conn, viewmode) + msg, body = self.content_view(viewmode, conn) return headers, msg, body def conn_text_merge(self, headers, msg, body): -- cgit v1.2.3 From cf9f91b0b4abe2020c544981d6dc2e2e85f4b4bd Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 14:33:42 +0100 Subject: web: upgrade to react 0.13 --- libmproxy/web/static/app.js | 44 +- libmproxy/web/static/vendor.css | 46 +- libmproxy/web/static/vendor.js | 11077 ++++++++++++++++++++++---------------- web/package.json | 100 +- web/src/js/components/common.js | 30 +- web/src/js/components/header.js | 14 +- 6 files changed, 6608 insertions(+), 4703 deletions(-) diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 2254b415..4f3998a9 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -478,31 +478,43 @@ var StickyHeadMixin = { var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { - var q = this.context.getCurrentQuery(); + var q = this.context.router.getCurrentQuery(); for(var i in dict){ if(dict.hasOwnProperty(i)){ q[i] = dict[i] || undefined; //falsey values shall be removed. } } q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/957 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); + routeNameOrPath = this.context.router.getCurrentPath(); } if(params === undefined){ - params = this.context.getCurrentParams(); + params = this.context.router.getCurrentParams(); } if(query === undefined) { - query = this.context.getCurrentQuery(); + query = this.context.router.getCurrentQuery(); } - // FIXME: react-router is just broken. - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + // FIXME: react-router is just broken, + // we hopefully just need to wait for the next release with https://github.com/rackt/react-router/pull/957. + this.context.router.replaceWith(routeNameOrPath, params, query); + } +}); + +// react-router is fairly good at changing its API regularly. +// We keep the old method for now - if it should turn out that their changes are permanent, +// we may remove this mixin and access react-router directly again. +var State = _.extend({}, ReactRouter.State, { + getQuery: function(){ + return this.context.router.getCurrentQuery(); + }, + getParams: function(){ + return this.context.router.getCurrentParams(); } }); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); var Splitter = React.createClass({displayName: "Splitter", getDefaultProps: function () { @@ -610,7 +622,7 @@ var Splitter = React.createClass({displayName: "Splitter", }); module.exports = { - State: ReactRouter.State, // keep here - react-router is pretty buggy, we may need workarounds in the future. + State: State, Navigation: Navigation, StickyHeadMixin: StickyHeadMixin, AutoScrollMixin: AutoScrollMixin, @@ -2166,15 +2178,17 @@ var Header = React.createClass({displayName: "Header", }, render: function () { var header = header_entries.map(function (entry, i) { - var classes = React.addons.classSet({ - active: entry == this.state.active - }); + var className; + if(entry === this.state.active){ + className = "active"; + } else { + className = ""; + } return ( React.createElement("a", {key: i, href: "#", - className: classes, - onClick: this.handleClick.bind(this, entry) - }, + className: className, + onClick: this.handleClick.bind(this, entry)}, entry.title ) ); diff --git a/libmproxy/web/static/vendor.css b/libmproxy/web/static/vendor.css index 149372c8..a170c49a 100644 --- a/libmproxy/web/static/vendor.css +++ b/libmproxy/web/static/vendor.css @@ -945,12 +945,24 @@ th { .glyphicon-bitcoin:before { content: "\e227"; } +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} .glyphicon-yen:before { content: "\00a5"; } +.glyphicon-jpy:before { + content: "\00a5"; +} .glyphicon-ruble:before { content: "\20bd"; } +.glyphicon-rub:before { + content: "\20bd"; +} .glyphicon-scale:before { content: "\e230"; } @@ -1147,6 +1159,9 @@ hr { overflow: visible; clip: auto; } +[role="button"] { + cursor: pointer; +} h1, h2, h3, @@ -2548,10 +2563,13 @@ output { .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - cursor: not-allowed; background-color: #eeeeee; opacity: 1; } +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} textarea.form-control { height: auto; } @@ -2618,6 +2636,7 @@ input[type="search"] { } .radio-inline, .checkbox-inline { + position: relative; display: inline-block; padding-left: 20px; margin-bottom: 0; @@ -2654,6 +2673,7 @@ fieldset[disabled] .checkbox label { padding-top: 7px; padding-bottom: 7px; margin-bottom: 0; + min-height: 34px; } .form-control-static.input-lg, .form-control-static.input-sm { @@ -2695,6 +2715,7 @@ select[multiple].form-group-sm .form-control { padding: 5px 10px; font-size: 12px; line-height: 1.5; + min-height: 32px; } .input-lg { height: 46px; @@ -2731,6 +2752,7 @@ select[multiple].form-group-lg .form-control { padding: 10px 16px; font-size: 18px; line-height: 1.3333333; + min-height: 38px; } .has-feedback { position: relative; @@ -3348,11 +3370,9 @@ input[type="button"].btn-block { } .collapse { display: none; - visibility: hidden; } .collapse.in { display: block; - visibility: visible; } tr.collapse.in { display: table-row; @@ -3377,7 +3397,7 @@ tbody.collapse.in { height: 0; margin-left: 2px; vertical-align: middle; - border-top: 4px solid; + border-top: 4px dashed; border-right: 4px solid transparent; border-left: 4px solid transparent; } @@ -4016,11 +4036,9 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .tab-content > .tab-pane { display: none; - visibility: hidden; } .tab-content > .active { display: block; - visibility: visible; } .nav-tabs .dropdown-menu { margin-top: -1px; @@ -4062,7 +4080,6 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-collapse.collapse { display: block !important; - visibility: visible !important; height: auto !important; padding-bottom: 0; overflow: visible !important; @@ -4791,7 +4808,8 @@ a.label:focus { position: relative; top: -1px; } -.btn-xs .badge { +.btn-xs .badge, +.btn-group-xs > .btn .badge { top: 0; padding: 1px 5px; } @@ -5614,10 +5632,10 @@ a.list-group-item-danger.active:focus { width: 100%; border: 0; } -.embed-responsive.embed-responsive-16by9 { +.embed-responsive-16by9 { padding-bottom: 56.25%; } -.embed-responsive.embed-responsive-4by3 { +.embed-responsive-4by3 { padding-bottom: 75%; } .well { @@ -5678,7 +5696,7 @@ button.close { right: 0; bottom: 0; left: 0; - z-index: 1040; + z-index: 1050; -webkit-overflow-scrolling: touch; outline: 0; } @@ -5719,10 +5737,12 @@ button.close { outline: 0; } .modal-backdrop { - position: absolute; + position: fixed; top: 0; right: 0; + bottom: 0; left: 0; + z-index: 1040; background-color: #000000; } .modal-backdrop.fade { @@ -5793,7 +5813,6 @@ button.close { position: absolute; z-index: 1070; display: block; - visibility: visible; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; font-weight: normal; @@ -6316,7 +6335,6 @@ button.close { } .hidden { display: none !important; - visibility: hidden !important; } .affix { position: fixed; diff --git a/libmproxy/web/static/vendor.js b/libmproxy/web/static/vendor.js index d98e50d9..6b34edb9 100644 --- a/libmproxy/web/static/vendor.js +++ b/libmproxy/web/static/vendor.js @@ -394,49 +394,432 @@ var invariant = function(condition, format, a, b, c, d, e, f) { module.exports = invariant; },{}],4:[function(require,module,exports){ -"use strict"; +module.exports = require('./lib/'); -/** - * Represents a cancellation caused by navigating away - * before the previous transition has fully resolved. - */ -function Cancellation() {} +},{"./lib/":5}],5:[function(require,module,exports){ +// Load modules -module.exports = Cancellation; -},{}],5:[function(require,module,exports){ -"use strict"; +var Stringify = require('./stringify'); +var Parse = require('./parse'); -var warning = require("react/lib/warning"); -var invariant = require("react/lib/invariant"); -function checkPropTypes(componentName, propTypes, props) { - for (var propName in propTypes) { - if (propTypes.hasOwnProperty(propName)) { - var error = propTypes[propName](props, propName, componentName); +// Declare internals - if (error instanceof Error) warning(false, error.message); +var internals = {}; + + +module.exports = { + stringify: Stringify, + parse: Parse +}; + +},{"./parse":6,"./stringify":7}],6:[function(require,module,exports){ +// Load modules + +var Utils = require('./utils'); + + +// Declare internals + +var internals = { + delimiter: '&', + depth: 5, + arrayLimit: 20, + parameterLimit: 1000 +}; + + +internals.parseValues = function (str, options) { + + var obj = {}; + var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit); + + for (var i = 0, il = parts.length; i < il; ++i) { + var part = parts[i]; + var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1; + + if (pos === -1) { + obj[Utils.decode(part)] = ''; + } + else { + var key = Utils.decode(part.slice(0, pos)); + var val = Utils.decode(part.slice(pos + 1)); + + if (Object.prototype.hasOwnProperty(key)) { + continue; + } + + if (!obj.hasOwnProperty(key)) { + obj[key] = val; + } + else { + obj[key] = [].concat(obj[key]).concat(val); + } + } } - } -} -var Configuration = { + return obj; +}; + - statics: { +internals.parseObject = function (chain, val, options) { - validateProps: function validateProps(props) { - checkPropTypes(this.displayName, this.propTypes, props); + if (!chain.length) { + return val; } - }, + var root = chain.shift(); - render: function render() { - invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.displayName); - } + var obj = {}; + if (root === '[]') { + obj = []; + obj = obj.concat(internals.parseObject(chain, val, options)); + } + else { + var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root; + var index = parseInt(cleanRoot, 10); + var indexString = '' + index; + if (!isNaN(index) && + root !== cleanRoot && + indexString === cleanRoot && + index >= 0 && + index <= options.arrayLimit) { + + obj = []; + obj[index] = internals.parseObject(chain, val, options); + } + else { + obj[cleanRoot] = internals.parseObject(chain, val, options); + } + } + + return obj; +}; + + +internals.parseKeys = function (key, val, options) { + + if (!key) { + return; + } + + // The regex chunks + + var parent = /^([^\[\]]*)/; + var child = /(\[[^\[\]]*\])/g; + + // Get the parent + + var segment = parent.exec(key); + + // Don't allow them to overwrite object prototype properties + + if (Object.prototype.hasOwnProperty(segment[1])) { + return; + } + + // Stash the parent if it exists + + var keys = []; + if (segment[1]) { + keys.push(segment[1]); + } + + // Loop through children appending to the array until we hit depth + + var i = 0; + while ((segment = child.exec(key)) !== null && i < options.depth) { + + ++i; + if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) { + keys.push(segment[1]); + } + } + + // If there's a remainder, just add whatever is left + + if (segment) { + keys.push('[' + key.slice(segment.index) + ']'); + } + + return internals.parseObject(keys, val, options); +}; + + +module.exports = function (str, options) { + + if (str === '' || + str === null || + typeof str === 'undefined') { + + return {}; + } + + options = options || {}; + options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter; + options.depth = typeof options.depth === 'number' ? options.depth : internals.depth; + options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit; + options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit; + + var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str; + var obj = {}; + + // Iterate over the keys and setup the new object + + var keys = Object.keys(tempObj); + for (var i = 0, il = keys.length; i < il; ++i) { + var key = keys[i]; + var newObj = internals.parseKeys(key, tempObj[key], options); + obj = Utils.merge(obj, newObj); + } + + return Utils.compact(obj); +}; + +},{"./utils":8}],7:[function(require,module,exports){ +// Load modules + +var Utils = require('./utils'); + + +// Declare internals + +var internals = { + delimiter: '&', + arrayPrefixGenerators: { + brackets: function (prefix, key) { + return prefix + '[]'; + }, + indices: function (prefix, key) { + return prefix + '[' + key + ']'; + }, + repeat: function (prefix, key) { + return prefix; + } + } +}; + + +internals.stringify = function (obj, prefix, generateArrayPrefix) { + + if (Utils.isBuffer(obj)) { + obj = obj.toString(); + } + else if (obj instanceof Date) { + obj = obj.toISOString(); + } + else if (obj === null) { + obj = ''; + } + + if (typeof obj === 'string' || + typeof obj === 'number' || + typeof obj === 'boolean') { + + return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)]; + } + + var values = []; + + if (typeof obj === 'undefined') { + return values; + } + + var objKeys = Object.keys(obj); + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + if (Array.isArray(obj)) { + values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix)); + } + else { + values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix)); + } + } + + return values; +}; + + +module.exports = function (obj, options) { + + options = options || {}; + var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter; + + var keys = []; + + if (typeof obj !== 'object' || + obj === null) { + + return ''; + } + + var arrayFormat; + if (options.arrayFormat in internals.arrayPrefixGenerators) { + arrayFormat = options.arrayFormat; + } + else if ('indices' in options) { + arrayFormat = options.indices ? 'indices' : 'repeat'; + } + else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat]; + + var objKeys = Object.keys(obj); + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix)); + } + + return keys.join(delimiter); +}; + +},{"./utils":8}],8:[function(require,module,exports){ +// Load modules + + +// Declare internals + +var internals = {}; + + +exports.arrayToObject = function (source) { + + var obj = {}; + for (var i = 0, il = source.length; i < il; ++i) { + if (typeof source[i] !== 'undefined') { + + obj[i] = source[i]; + } + } + + return obj; +}; + + +exports.merge = function (target, source) { + + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (Array.isArray(target)) { + target.push(source); + } + else { + target[source] = true; + } + + return target; + } + + if (typeof target !== 'object') { + target = [target].concat(source); + return target; + } + + if (Array.isArray(target) && + !Array.isArray(source)) { + + target = exports.arrayToObject(target); + } + + var keys = Object.keys(source); + for (var k = 0, kl = keys.length; k < kl; ++k) { + var key = keys[k]; + var value = source[key]; + + if (!target[key]) { + target[key] = value; + } + else { + target[key] = exports.merge(target[key], value); + } + } + + return target; +}; + + +exports.decode = function (str) { + + try { + return decodeURIComponent(str.replace(/\+/g, ' ')); + } catch (e) { + return str; + } +}; + + +exports.compact = function (obj, refs) { + + if (typeof obj !== 'object' || + obj === null) { + + return obj; + } + + refs = refs || []; + var lookup = refs.indexOf(obj); + if (lookup !== -1) { + return refs[lookup]; + } + + refs.push(obj); + + if (Array.isArray(obj)) { + var compacted = []; + + for (var i = 0, il = obj.length; i < il; ++i) { + if (typeof obj[i] !== 'undefined') { + compacted.push(obj[i]); + } + } + + return compacted; + } + + var keys = Object.keys(obj); + for (i = 0, il = keys.length; i < il; ++i) { + var key = keys[i]; + obj[key] = exports.compact(obj[key], refs); + } + + return obj; +}; + + +exports.isRegExp = function (obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + + +exports.isBuffer = function (obj) { + if (obj === null || + typeof obj === 'undefined') { + + return false; + } + + return !!(obj.constructor && + obj.constructor.isBuffer && + obj.constructor.isBuffer(obj)); }; -module.exports = Configuration; -},{"react/lib/invariant":182,"react/lib/warning":202}],6:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ +"use strict"; + +/** + * Represents a cancellation caused by navigating away + * before the previous transition has fully resolved. + */ +function Cancellation() {} + +module.exports = Cancellation; +},{}],10:[function(require,module,exports){ "use strict"; var invariant = require("react/lib/invariant"); @@ -467,10 +850,10 @@ var History = { }; module.exports = History; -},{"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],7:[function(require,module,exports){ +},{"react/lib/ExecutionEnvironment":60,"react/lib/invariant":189}],11:[function(require,module,exports){ "use strict"; -var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; @@ -520,7 +903,7 @@ var Match = (function () { this.routes = routes; } - _prototypeProperties(Match, { + _createClass(Match, null, { findMatch: { /** @@ -537,9 +920,7 @@ var Match = (function () { for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query); return match; - }, - writable: true, - configurable: true + } } }); @@ -547,11 +928,20 @@ var Match = (function () { })(); module.exports = Match; -},{"./PathUtils":10}],8:[function(require,module,exports){ +},{"./PathUtils":13}],12:[function(require,module,exports){ "use strict"; +var warning = require("react/lib/warning"); var PropTypes = require("./PropTypes"); +function deprecatedMethod(routerMethodName, fn) { + return function () { + warning(false, "Router.Navigation is deprecated. Please use this.context.router." + routerMethodName + "() instead"); + + return fn.apply(this, arguments); + }; +} + /** * A mixin for components that modify the URL. * @@ -559,11 +949,11 @@ var PropTypes = require("./PropTypes"); * * var MyLink = React.createClass({ * mixins: [ Router.Navigation ], - * handleClick: function (event) { + * handleClick(event) { * event.preventDefault(); * this.transitionTo('aRoute', { the: 'params' }, { the: 'query' }); * }, - * render: function () { + * render() { * return ( * Click me! * ); @@ -573,87 +963,52 @@ var PropTypes = require("./PropTypes"); var Navigation = { contextTypes: { - makePath: PropTypes.func.isRequired, - makeHref: PropTypes.func.isRequired, - transitionTo: PropTypes.func.isRequired, - replaceWith: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired + router: PropTypes.router.isRequired }, /** * Returns an absolute URL path created from the given route * name, URL parameters, and query values. */ - makePath: function makePath(to, params, query) { - return this.context.makePath(to, params, query); - }, + makePath: deprecatedMethod("makePath", function (to, params, query) { + return this.context.router.makePath(to, params, query); + }), /** * Returns a string that may safely be used as the href of a * link to the route with the given name. */ - makeHref: function makeHref(to, params, query) { - return this.context.makeHref(to, params, query); - }, + makeHref: deprecatedMethod("makeHref", function (to, params, query) { + return this.context.router.makeHref(to, params, query); + }), /** * Transitions to the URL specified in the arguments by pushing * a new URL onto the history stack. */ - transitionTo: function transitionTo(to, params, query) { - this.context.transitionTo(to, params, query); - }, + transitionTo: deprecatedMethod("transitionTo", function (to, params, query) { + this.context.router.transitionTo(to, params, query); + }), /** * Transitions to the URL specified in the arguments by replacing * the current URL in the history stack. */ - replaceWith: function replaceWith(to, params, query) { - this.context.replaceWith(to, params, query); - }, + replaceWith: deprecatedMethod("replaceWith", function (to, params, query) { + this.context.router.replaceWith(to, params, query); + }), /** * Transitions to the previous URL. */ - goBack: function goBack() { - return this.context.goBack(); - } + goBack: deprecatedMethod("goBack", function () { + return this.context.router.goBack(); + }) }; module.exports = Navigation; -},{"./PropTypes":11}],9:[function(require,module,exports){ -"use strict"; - -var PropTypes = require("./PropTypes"); - -/** - * Provides the router with context for Router.Navigation. - */ -var NavigationContext = { - - childContextTypes: { - makePath: PropTypes.func.isRequired, - makeHref: PropTypes.func.isRequired, - transitionTo: PropTypes.func.isRequired, - replaceWith: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired - }, - - getChildContext: function getChildContext() { - return { - makePath: this.constructor.makePath.bind(this.constructor), - makeHref: this.constructor.makeHref.bind(this.constructor), - transitionTo: this.constructor.transitionTo.bind(this.constructor), - replaceWith: this.constructor.replaceWith.bind(this.constructor), - goBack: this.constructor.goBack.bind(this.constructor) - }; - } - -}; - -module.exports = NavigationContext; -},{"./PropTypes":11}],10:[function(require,module,exports){ +},{"./PropTypes":14,"react/lib/warning":210}],13:[function(require,module,exports){ "use strict"; var invariant = require("react/lib/invariant"); @@ -797,7 +1152,7 @@ var PathUtils = { if (existingQuery) query = query ? merge(existingQuery, query) : existingQuery; - var queryString = qs.stringify(query, { indices: false }); + var queryString = qs.stringify(query, { arrayFormat: "brackets" }); if (queryString) { return PathUtils.withoutQuery(path) + "?" + queryString; @@ -807,27 +1162,39 @@ var PathUtils = { }; module.exports = PathUtils; -},{"qs":38,"qs/lib/utils":42,"react/lib/invariant":182}],11:[function(require,module,exports){ +},{"qs":4,"qs/lib/utils":8,"react/lib/invariant":189}],14:[function(require,module,exports){ "use strict"; var assign = require("react/lib/Object.assign"); var ReactPropTypes = require("react").PropTypes; +var Route = require("./Route"); -var PropTypes = assign({ +var PropTypes = assign({}, ReactPropTypes, { /** - * Requires that the value of a prop be falsy. + * Indicates that a prop should be falsy. */ falsy: function falsy(props, propName, componentName) { if (props[propName]) { return new Error("<" + componentName + "> may not have a \"" + propName + "\" prop"); } - } + }, + + /** + * Indicates that a prop should be a Route object. + */ + route: ReactPropTypes.instanceOf(Route), -}, ReactPropTypes); + /** + * Indicates that a prop should be a Router object. + */ + //router: ReactPropTypes.instanceOf(Router) // TODO + router: ReactPropTypes.func + +}); module.exports = PropTypes; -},{"react":"react","react/lib/Object.assign":70}],12:[function(require,module,exports){ +},{"./Route":16,"react":"react","react/lib/Object.assign":67}],15:[function(require,module,exports){ "use strict"; /** @@ -840,10 +1207,10 @@ function Redirect(to, params, query) { } module.exports = Redirect; -},{}],13:[function(require,module,exports){ +},{}],16:[function(require,module,exports){ "use strict"; -var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; @@ -869,7 +1236,33 @@ var Route = (function () { this.handler = handler; } - _prototypeProperties(Route, { + _createClass(Route, { + appendChild: { + + /** + * Appends the given route to this route's child routes. + */ + + value: function appendChild(route) { + invariant(route instanceof Route, "route.appendChild must use a valid Route"); + + if (!this.childRoutes) this.childRoutes = []; + + this.childRoutes.push(route); + } + }, + toString: { + value: function toString() { + var string = ""; + + return string; + } + } + }, { createRoute: { /** @@ -928,7 +1321,7 @@ var Route = (function () { if (path && !(options.isDefault || options.isNotFound)) { if (PathUtils.isAbsolute(path)) { if (parentRoute) { - invariant(parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path); + invariant(path === parentRoute.path || parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path); } } else if (parentRoute) { // Relative paths extend their parent. @@ -968,9 +1361,7 @@ var Route = (function () { } return route; - }, - writable: true, - configurable: true + } }, createDefaultRoute: { @@ -981,9 +1372,7 @@ var Route = (function () { value: function createDefaultRoute(options) { return Route.createRoute(assign({}, options, { isDefault: true })); - }, - writable: true, - configurable: true + } }, createNotFoundRoute: { @@ -994,9 +1383,7 @@ var Route = (function () { value: function createNotFoundRoute(options) { return Route.createRoute(assign({}, options, { isNotFound: true })); - }, - writable: true, - configurable: true + } }, createRedirect: { @@ -1020,39 +1407,7 @@ var Route = (function () { transition.redirect(options.to, options.params || params, options.query || query); } })); - }, - writable: true, - configurable: true - } - }, { - appendChild: { - - /** - * Appends the given route to this route's child routes. - */ - - value: function appendChild(route) { - invariant(route instanceof Route, "route.appendChild must use a valid Route"); - - if (!this.childRoutes) this.childRoutes = []; - - this.childRoutes.push(route); - }, - writable: true, - configurable: true - }, - toString: { - value: function toString() { - var string = ""; - - return string; - }, - writable: true, - configurable: true + } } }); @@ -1060,67 +1415,12 @@ var Route = (function () { })(); module.exports = Route; -},{"./PathUtils":10,"react/lib/Object.assign":70,"react/lib/invariant":182,"react/lib/warning":202}],14:[function(require,module,exports){ +},{"./PathUtils":13,"react/lib/Object.assign":67,"react/lib/invariant":189,"react/lib/warning":210}],17:[function(require,module,exports){ "use strict"; -var React = require("react"); -var assign = require("react/lib/Object.assign"); -var PropTypes = require("./PropTypes"); - -var REF_NAME = "__routeHandler__"; - -var RouteHandlerMixin = { - - contextTypes: { - getRouteAtDepth: PropTypes.func.isRequired, - setRouteComponentAtDepth: PropTypes.func.isRequired, - routeHandlers: PropTypes.array.isRequired - }, - - childContextTypes: { - routeHandlers: PropTypes.array.isRequired - }, - - getChildContext: function getChildContext() { - return { - routeHandlers: this.context.routeHandlers.concat([this]) - }; - }, - - componentDidMount: function componentDidMount() { - this._updateRouteComponent(this.refs[REF_NAME]); - }, - - componentDidUpdate: function componentDidUpdate() { - this._updateRouteComponent(this.refs[REF_NAME]); - }, - - componentWillUnmount: function componentWillUnmount() { - this._updateRouteComponent(null); - }, - - _updateRouteComponent: function _updateRouteComponent(component) { - this.context.setRouteComponentAtDepth(this.getRouteDepth(), component); - }, - - getRouteDepth: function getRouteDepth() { - return this.context.routeHandlers.length; - }, - - createChildRouteHandler: function createChildRouteHandler(props) { - var route = this.context.getRouteAtDepth(this.getRouteDepth()); - return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; - } - -}; - -module.exports = RouteHandlerMixin; -},{"./PropTypes":11,"react":"react","react/lib/Object.assign":70}],15:[function(require,module,exports){ -"use strict"; - -var invariant = require("react/lib/invariant"); -var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; -var getWindowScrollPosition = require("./getWindowScrollPosition"); +var invariant = require("react/lib/invariant"); +var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; +var getWindowScrollPosition = require("./getWindowScrollPosition"); function shouldUpdateScroll(state, prevState) { if (!prevState) { @@ -1191,11 +1491,20 @@ var ScrollHistory = { }; module.exports = ScrollHistory; -},{"./getWindowScrollPosition":30,"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],16:[function(require,module,exports){ +},{"./getWindowScrollPosition":32,"react/lib/ExecutionEnvironment":60,"react/lib/invariant":189}],18:[function(require,module,exports){ "use strict"; +var warning = require("react/lib/warning"); var PropTypes = require("./PropTypes"); +function deprecatedMethod(routerMethodName, fn) { + return function () { + warning(false, "Router.State is deprecated. Please use this.context.router." + routerMethodName + "() instead"); + + return fn.apply(this, arguments); + }; +} + /** * A mixin for components that need to know the path, routes, URL * params and query that are currently active. @@ -1204,7 +1513,7 @@ var PropTypes = require("./PropTypes"); * * var AboutLink = React.createClass({ * mixins: [ Router.State ], - * render: function () { + * render() { * var className = this.props.className; * * if (this.isActive('about')) @@ -1217,158 +1526,56 @@ var PropTypes = require("./PropTypes"); var State = { contextTypes: { - getCurrentPath: PropTypes.func.isRequired, - getCurrentRoutes: PropTypes.func.isRequired, - getCurrentPathname: PropTypes.func.isRequired, - getCurrentParams: PropTypes.func.isRequired, - getCurrentQuery: PropTypes.func.isRequired, - isActive: PropTypes.func.isRequired + router: PropTypes.router.isRequired }, /** * Returns the current URL path. */ - getPath: function getPath() { - return this.context.getCurrentPath(); - }, - - /** - * Returns an array of the routes that are currently active. - */ - getRoutes: function getRoutes() { - return this.context.getCurrentRoutes(); - }, + getPath: deprecatedMethod("getCurrentPath", function () { + return this.context.router.getCurrentPath(); + }), /** * Returns the current URL path without the query string. */ - getPathname: function getPathname() { - return this.context.getCurrentPathname(); - }, + getPathname: deprecatedMethod("getCurrentPathname", function () { + return this.context.router.getCurrentPathname(); + }), /** * Returns an object of the URL params that are currently active. */ - getParams: function getParams() { - return this.context.getCurrentParams(); - }, + getParams: deprecatedMethod("getCurrentParams", function () { + return this.context.router.getCurrentParams(); + }), /** * Returns an object of the query params that are currently active. */ - getQuery: function getQuery() { - return this.context.getCurrentQuery(); - }, - - /** - * A helper method to determine if a given route, params, and query - * are active. - */ - isActive: function isActive(to, params, query) { - return this.context.isActive(to, params, query); - } - -}; - -module.exports = State; -},{"./PropTypes":11}],17:[function(require,module,exports){ -"use strict"; - -var assign = require("react/lib/Object.assign"); -var PropTypes = require("./PropTypes"); -var PathUtils = require("./PathUtils"); - -function routeIsActive(activeRoutes, routeName) { - return activeRoutes.some(function (route) { - return route.name === routeName; - }); -} - -function paramsAreActive(activeParams, params) { - for (var property in params) if (String(activeParams[property]) !== String(params[property])) { - return false; - }return true; -} - -function queryIsActive(activeQuery, query) { - for (var property in query) if (String(activeQuery[property]) !== String(query[property])) { - return false; - }return true; -} - -/** - * Provides the router with context for Router.State. - */ -var StateContext = { - - /** - * Returns the current URL path + query string. - */ - getCurrentPath: function getCurrentPath() { - return this.state.path; - }, - - /** - * Returns a read-only array of the currently active routes. - */ - getCurrentRoutes: function getCurrentRoutes() { - return this.state.routes.slice(0); - }, - - /** - * Returns the current URL path without the query string. - */ - getCurrentPathname: function getCurrentPathname() { - return this.state.pathname; - }, + getQuery: deprecatedMethod("getCurrentQuery", function () { + return this.context.router.getCurrentQuery(); + }), /** - * Returns a read-only object of the currently active URL parameters. + * Returns an array of the routes that are currently active. */ - getCurrentParams: function getCurrentParams() { - return assign({}, this.state.params); - }, + getRoutes: deprecatedMethod("getCurrentRoutes", function () { + return this.context.router.getCurrentRoutes(); + }), /** - * Returns a read-only object of the currently active query parameters. - */ - getCurrentQuery: function getCurrentQuery() { - return assign({}, this.state.query); - }, - - /** - * Returns true if the given route, params, and query are active. + * A helper method to determine if a given route, params, and query + * are active. */ - isActive: function isActive(to, params, query) { - if (PathUtils.isAbsolute(to)) { - return to === this.state.path; - }return routeIsActive(this.state.routes, to) && paramsAreActive(this.state.params, params) && (query == null || queryIsActive(this.state.query, query)); - }, - - childContextTypes: { - getCurrentPath: PropTypes.func.isRequired, - getCurrentRoutes: PropTypes.func.isRequired, - getCurrentPathname: PropTypes.func.isRequired, - getCurrentParams: PropTypes.func.isRequired, - getCurrentQuery: PropTypes.func.isRequired, - isActive: PropTypes.func.isRequired - }, - - getChildContext: function getChildContext() { - return { - getCurrentPath: this.getCurrentPath, - getCurrentRoutes: this.getCurrentRoutes, - getCurrentPathname: this.getCurrentPathname, - getCurrentParams: this.getCurrentParams, - getCurrentQuery: this.getCurrentQuery, - isActive: this.isActive - }; - } + isActive: deprecatedMethod("isActive", function (to, params, query) { + return this.context.router.isActive(to, params, query); + }) }; -module.exports = StateContext; -},{"./PathUtils":10,"./PropTypes":11,"react/lib/Object.assign":70}],18:[function(require,module,exports){ +module.exports = State; +},{"./PropTypes":14,"react/lib/warning":210}],19:[function(require,module,exports){ "use strict"; /* jshint -W058 */ @@ -1444,7 +1651,7 @@ Transition.to = function (transition, routes, params, query, callback) { }; module.exports = Transition; -},{"./Cancellation":4,"./Redirect":12}],19:[function(require,module,exports){ +},{"./Cancellation":9,"./Redirect":15}],20:[function(require,module,exports){ "use strict"; /** @@ -1470,7 +1677,7 @@ var LocationActions = { }; module.exports = LocationActions; -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ "use strict"; var LocationActions = require("../actions/LocationActions"); @@ -1500,7 +1707,7 @@ var ImitateBrowserBehavior = { }; module.exports = ImitateBrowserBehavior; -},{"../actions/LocationActions":19}],21:[function(require,module,exports){ +},{"../actions/LocationActions":20}],22:[function(require,module,exports){ "use strict"; /** @@ -1516,12 +1723,56 @@ var ScrollToTopBehavior = { }; module.exports = ScrollToTopBehavior; -},{}],22:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + +/** + * This component is necessary to get around a context warning + * present in React 0.13.0. It sovles this by providing a separation + * between the "owner" and "parent" contexts. + */ + var React = require("react"); -var Configuration = require("../Configuration"); + +var ContextWrapper = (function (_React$Component) { + function ContextWrapper() { + _classCallCheck(this, ContextWrapper); + + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } + + _inherits(ContextWrapper, _React$Component); + + _createClass(ContextWrapper, { + render: { + value: function render() { + return this.props.children; + } + } + }); + + return ContextWrapper; +})(React.Component); + +module.exports = ContextWrapper; +},{"react":"react"}],24:[function(require,module,exports){ +"use strict"; + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var RouteHandler = require("./RouteHandler"); +var Route = require("./Route"); /** * A component is a special kind of that @@ -1529,32 +1780,49 @@ var PropTypes = require("../PropTypes"); * Only one such route may be used at any given level in the * route hierarchy. */ -var DefaultRoute = React.createClass({ - displayName: "DefaultRoute", +var DefaultRoute = (function (_Route) { + function DefaultRoute() { + _classCallCheck(this, DefaultRoute); - mixins: [Configuration], - - propTypes: { - name: PropTypes.string, - path: PropTypes.falsy, - children: PropTypes.falsy, - handler: PropTypes.func.isRequired + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(DefaultRoute, _Route); + + return DefaultRoute; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +DefaultRoute.propTypes = { + name: PropTypes.string, + path: PropTypes.falsy, + children: PropTypes.falsy, + handler: PropTypes.func.isRequired +}; + +DefaultRoute.defaultProps = { + handler: RouteHandler +}; module.exports = DefaultRoute; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],23:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],25:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var classSet = require("react/lib/cx"); var assign = require("react/lib/Object.assign"); -var Navigation = require("../Navigation"); -var State = require("../State"); var PropTypes = require("../PropTypes"); -var Route = require("../Route"); function isLeftClickEvent(event) { return event.button === 0; @@ -1582,88 +1850,116 @@ function isModifiedEvent(event) { * * */ -var Link = React.createClass({ - displayName: "Link", +var Link = (function (_React$Component) { + function Link() { + _classCallCheck(this, Link); - mixins: [Navigation, State], + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } - propTypes: { - activeClassName: PropTypes.string.isRequired, - to: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Route)]), - params: PropTypes.object, - query: PropTypes.object, - activeStyle: PropTypes.object, - onClick: PropTypes.func - }, + _inherits(Link, _React$Component); - getDefaultProps: function getDefaultProps() { - return { - activeClassName: "active" - }; - }, + _createClass(Link, { + handleClick: { + value: function handleClick(event) { + var allowTransition = true; + var clickResult; - handleClick: function handleClick(event) { - var allowTransition = true; - var clickResult; + if (this.props.onClick) clickResult = this.props.onClick(event); - if (this.props.onClick) clickResult = this.props.onClick(event); + if (isModifiedEvent(event) || !isLeftClickEvent(event)) { + return; + }if (clickResult === false || event.defaultPrevented === true) allowTransition = false; - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - }if (clickResult === false || event.defaultPrevented === true) allowTransition = false; + event.preventDefault(); - event.preventDefault(); + if (allowTransition) this.context.router.transitionTo(this.props.to, this.props.params, this.props.query); + } + }, + getHref: { - if (allowTransition) this.transitionTo(this.props.to, this.props.params, this.props.query); - }, + /** + * Returns the value of the "href" attribute to use on the DOM element. + */ - /** - * Returns the value of the "href" attribute to use on the DOM element. - */ - getHref: function getHref() { - return this.makeHref(this.props.to, this.props.params, this.props.query); - }, + value: function getHref() { + return this.context.router.makeHref(this.props.to, this.props.params, this.props.query); + } + }, + getClassName: { - /** - * Returns the value of the "class" attribute to use on the DOM element, which contains - * the value of the activeClassName property when this is active. - */ - getClassName: function getClassName() { - var classNames = {}; + /** + * Returns the value of the "class" attribute to use on the DOM element, which contains + * the value of the activeClassName property when this is active. + */ - if (this.props.className) classNames[this.props.className] = true; + value: function getClassName() { + var className = this.props.className; - if (this.getActiveState()) classNames[this.props.activeClassName] = true; + if (this.getActiveState()) className += " " + this.props.activeClassName; - return classSet(classNames); - }, + return className; + } + }, + getActiveState: { + value: function getActiveState() { + return this.context.router.isActive(this.props.to, this.props.params, this.props.query); + } + }, + render: { + value: function render() { + var props = assign({}, this.props, { + href: this.getHref(), + className: this.getClassName(), + onClick: this.handleClick.bind(this) + }); - getActiveState: function getActiveState() { - return this.isActive(this.props.to, this.props.params, this.props.query); - }, + if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle; - render: function render() { - var props = assign({}, this.props, { - href: this.getHref(), - className: this.getClassName(), - onClick: this.handleClick - }); + return React.DOM.a(props, this.props.children); + } + } + }); - if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle; + return Link; +})(React.Component); - return React.DOM.a(props, this.props.children); - } +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 -}); +Link.contextTypes = { + router: PropTypes.router.isRequired +}; + +Link.propTypes = { + activeClassName: PropTypes.string.isRequired, + to: PropTypes.oneOfType([PropTypes.string, PropTypes.route]).isRequired, + params: PropTypes.object, + query: PropTypes.object, + activeStyle: PropTypes.object, + onClick: PropTypes.func +}; + +Link.defaultProps = { + activeClassName: "active", + className: "" +}; module.exports = Link; -},{"../Navigation":8,"../PropTypes":11,"../Route":13,"../State":16,"react":"react","react/lib/Object.assign":70,"react/lib/cx":160}],24:[function(require,module,exports){ +},{"../PropTypes":14,"react":"react","react/lib/Object.assign":67}],26:[function(require,module,exports){ "use strict"; -var React = require("react"); -var Configuration = require("../Configuration"); +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var RouteHandler = require("./RouteHandler"); +var Route = require("./Route"); /** * A is a special kind of that @@ -1672,56 +1968,95 @@ var PropTypes = require("../PropTypes"); * Only one such route may be used at any given level in the * route hierarchy. */ -var NotFoundRoute = React.createClass({ - - displayName: "NotFoundRoute", - mixins: [Configuration], +var NotFoundRoute = (function (_Route) { + function NotFoundRoute() { + _classCallCheck(this, NotFoundRoute); - propTypes: { - name: PropTypes.string, - path: PropTypes.falsy, - children: PropTypes.falsy, - handler: PropTypes.func.isRequired + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(NotFoundRoute, _Route); + + return NotFoundRoute; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +NotFoundRoute.propTypes = { + name: PropTypes.string, + path: PropTypes.falsy, + children: PropTypes.falsy, + handler: PropTypes.func.isRequired +}; + +NotFoundRoute.defaultProps = { + handler: RouteHandler +}; module.exports = NotFoundRoute; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],25:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],27:[function(require,module,exports){ "use strict"; -var React = require("react"); -var Configuration = require("../Configuration"); +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var Route = require("./Route"); /** * A component is a special kind of that always * redirects to another route when it matches. */ -var Redirect = React.createClass({ - displayName: "Redirect", +var Redirect = (function (_Route) { + function Redirect() { + _classCallCheck(this, Redirect); - mixins: [Configuration], - - propTypes: { - path: PropTypes.string, - from: PropTypes.string, // Alias for path. - to: PropTypes.string, - handler: PropTypes.falsy + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(Redirect, _Route); + + return Redirect; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +Redirect.propTypes = { + path: PropTypes.string, + from: PropTypes.string, // Alias for path. + to: PropTypes.string, + handler: PropTypes.falsy +}; + +// Redirects should not have a default handler +Redirect.defaultProps = {}; module.exports = Redirect; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],26:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28}],28:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var Configuration = require("../Configuration"); +var invariant = require("react/lib/invariant"); var PropTypes = require("../PropTypes"); var RouteHandler = require("./RouteHandler"); + /** * components specify components that are rendered to the page when the * URL matches a given pattern. @@ -1762,52 +2097,147 @@ var RouteHandler = require("./RouteHandler"); * * If no handler is provided for the route, it will render a matched child route. */ -var Route = React.createClass({ - displayName: "Route", +var Route = (function (_React$Component) { + function Route() { + _classCallCheck(this, Route); - mixins: [Configuration], + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } - propTypes: { - name: PropTypes.string, - path: PropTypes.string, - handler: PropTypes.func, - ignoreScrollBehavior: PropTypes.bool - }, + _inherits(Route, _React$Component); - getDefaultProps: function getDefaultProps() { - return { - handler: RouteHandler - }; - } + _createClass(Route, { + render: { + value: function render() { + invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.name); + } + } + }); -}); + return Route; +})(React.Component); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +Route.propTypes = { + name: PropTypes.string, + path: PropTypes.string, + handler: PropTypes.func, + ignoreScrollBehavior: PropTypes.bool +}; + +Route.defaultProps = { + handler: RouteHandler +}; module.exports = Route; -},{"../Configuration":5,"../PropTypes":11,"./RouteHandler":27,"react":"react"}],27:[function(require,module,exports){ +},{"../PropTypes":14,"./RouteHandler":29,"react":"react","react/lib/invariant":189}],29:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var RouteHandlerMixin = require("../RouteHandlerMixin"); +var ContextWrapper = require("./ContextWrapper"); +var assign = require("react/lib/Object.assign"); +var PropTypes = require("../PropTypes"); + +var REF_NAME = "__routeHandler__"; /** * A component renders the active child route handler * when routes are nested. */ -var RouteHandler = React.createClass({ - - displayName: "RouteHandler", - mixins: [RouteHandlerMixin], +var RouteHandler = (function (_React$Component) { + function RouteHandler() { + _classCallCheck(this, RouteHandler); - render: function render() { - return this.createChildRouteHandler(); + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } } -}); + _inherits(RouteHandler, _React$Component); + + _createClass(RouteHandler, { + getChildContext: { + value: function getChildContext() { + return { + routeDepth: this.context.routeDepth + 1 + }; + } + }, + componentDidMount: { + value: function componentDidMount() { + this._updateRouteComponent(this.refs[REF_NAME]); + } + }, + componentDidUpdate: { + value: function componentDidUpdate() { + this._updateRouteComponent(this.refs[REF_NAME]); + } + }, + componentWillUnmount: { + value: function componentWillUnmount() { + this._updateRouteComponent(null); + } + }, + _updateRouteComponent: { + value: function _updateRouteComponent(component) { + this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); + } + }, + getRouteDepth: { + value: function getRouteDepth() { + return this.context.routeDepth; + } + }, + createChildRouteHandler: { + value: function createChildRouteHandler(props) { + var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); + return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; + } + }, + render: { + value: function render() { + var handler = this.createChildRouteHandler(); + // diff --git a/setup.py b/setup.py index bffce805..9509ce7a 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,8 @@ setup( "coveralls>=0.4.1", "pathod>=%s, <%s" % ( version.MINORVERSION, version.NEXT_MINORVERSION - ) + ), + "countershape" ], 'contentviews': [ "pyamf>=0.6.1", -- cgit v1.2.3 From b2f87b9424f825874732c160de109d4385ae59e5 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Thu, 21 May 2015 13:15:55 +0200 Subject: improve autoenv script * properly quote DIR variable (it might contain spaces) * use builtin string magic instead of `dirname` --- .env | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env b/.env index 7f847e29..97f38452 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ -DIR=`dirname $0` -if [ -z "$VIRTUAL_ENV" ] && [ -f $DIR/../venv.mitmproxy/bin/activate ]; then +DIR="${0%/*}" +if [ -z "$VIRTUAL_ENV" ] && [ -f "$DIR/../venv.mitmproxy/bin/activate" ]; then echo "Activating mitmproxy virtualenv..." - source $DIR/../venv.mitmproxy/bin/activate + source "$DIR/../venv.mitmproxy/bin/activate" fi -- cgit v1.2.3 From f816246f4eae78a5fb0626f753e5210fcbe9e82f Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 22 May 2015 11:21:00 +1200 Subject: doc: fix nav in website mode --- doc-src/_websitelayout.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc-src/_websitelayout.html b/doc-src/_websitelayout.html index 92bf7bc7..5a7c01bb 100644 --- a/doc-src/_websitelayout.html +++ b/doc-src/_websitelayout.html @@ -16,9 +16,9 @@ -- cgit v1.2.3 From ba070bf9474fc6db70a6693b1603503682476776 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Fri, 22 May 2015 17:13:58 +1200 Subject: docs: bootstrap theme + logo --- doc-src/01-bootstrap.min.css | 5 - doc-src/01-vendor.css | 6707 ++++++++++++++++++++++++++++++++++++++++++ doc-src/_layout.html | 7 +- doc-src/mitmproxy-long.png | Bin 0 -> 123829 bytes 4 files changed, 6713 insertions(+), 6 deletions(-) delete mode 100644 doc-src/01-bootstrap.min.css create mode 100644 doc-src/01-vendor.css create mode 100644 doc-src/mitmproxy-long.png diff --git a/doc-src/01-bootstrap.min.css b/doc-src/01-bootstrap.min.css deleted file mode 100644 index cd1c616a..00000000 --- a/doc-src/01-bootstrap.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.4 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/doc-src/01-vendor.css b/doc-src/01-vendor.css new file mode 100644 index 00000000..2f65c8a3 --- /dev/null +++ b/doc-src/01-vendor.css @@ -0,0 +1,6707 @@ +@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,700"); +.navbar-inverse .badge { + background-color: #fff; + color: #2780e3; +} +body { + -webkit-font-smoothing: antialiased; +} +.text-primary, +.text-primary:hover { + color: #2780e3; +} +.text-success, +.text-success:hover { + color: #3fb618; +} +.text-danger, +.text-danger:hover { + color: #ff0039; +} +.text-warning, +.text-warning:hover { + color: #ff7518; +} +.text-info, +.text-info:hover { + color: #9954bb; +} +table a:not(.btn), +.table a:not(.btn) { + text-decoration: underline; +} +table .dropdown-menu a, +.table .dropdown-menu a { + text-decoration: none; +} +table .success, +.table .success, +table .warning, +.table .warning, +table .danger, +.table .danger, +table .info, +.table .info { + color: #fff; +} +table .success a, +.table .success a, +table .warning a, +.table .warning a, +table .danger a, +.table .danger a, +table .info a, +.table .info a { + color: #fff; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .form-control-feedback { + color: #ff7518; +} +.has-warning .form-control, +.has-warning .form-control:focus, +.has-warning .input-group-addon { + border: 1px solid #ff7518; +} +.has-error .help-block, +.has-error .control-label, +.has-error .form-control-feedback { + color: #ff0039; +} +.has-error .form-control, +.has-error .form-control:focus, +.has-error .input-group-addon { + border: 1px solid #ff0039; +} +.has-success .help-block, +.has-success .control-label, +.has-success .form-control-feedback { + color: #3fb618; +} +.has-success .form-control, +.has-success .form-control:focus, +.has-success .input-group-addon { + border: 1px solid #3fb618; +} +.nav-pills > li > a { + border-radius: 0; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-image: none; +} +.close { + text-decoration: none; + text-shadow: none; + opacity: 0.4; +} +.close:hover, +.close:focus { + opacity: 1; +} +.alert { + border: none; +} +.alert .alert-link { + text-decoration: underline; + color: #fff; +} +.label { + border-radius: 0; +} +.progress { + height: 8px; + -webkit-box-shadow: none; + box-shadow: none; +} +.progress .progress-bar { + font-size: 8px; + line-height: 8px; +} +.panel-heading, +.panel-footer { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.panel-default .close { + color: #333333; +} +a.list-group-item-success.active { + background-color: #3fb618; +} +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + background-color: #379f15; +} +a.list-group-item-warning.active { + background-color: #ff7518; +} +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + background-color: #fe6600; +} +a.list-group-item-danger.active { + background-color: #ff0039; +} +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + background-color: #e60033; +} +.modal .close { + color: #333333; +} +.popover { + color: #333333; +} +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + color: inherit; + font: inherit; + margin: 0; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { + border: 0; + padding: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; + box-shadow: none !important; + text-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + select { + background: #fff !important; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Source Sans Pro", Calibri, Candara, Arial, sans-serif; + font-size: 15px; + line-height: 1.42857143; + color: #333333; + background-color: #ffffff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #2780e3; + text-decoration: none; +} +a:hover, +a:focus { + color: #165ba8; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 0; +} +.img-thumbnail { + padding: 4px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 0; + -webkit-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 21px; + margin-bottom: 21px; + border: 0; + border-top: 1px solid #e6e6e6; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: "Source Sans Pro", Calibri, Candara, Arial, sans-serif; + font-weight: 300; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #999999; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 21px; + margin-bottom: 10.5px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10.5px; + margin-bottom: 10.5px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 39px; +} +h2, +.h2 { + font-size: 32px; +} +h3, +.h3 { + font-size: 26px; +} +h4, +.h4 { + font-size: 19px; +} +h5, +.h5 { + font-size: 15px; +} +h6, +.h6 { + font-size: 13px; +} +p { + margin: 0 0 10.5px; +} +.lead { + margin-bottom: 21px; + font-size: 17px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 22.5px; + } +} +small, +.small { + font-size: 86%; +} +mark, +.mark { + background-color: #ff7518; + padding: .2em; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #999999; +} +.text-primary { + color: #2780e3; +} +a.text-primary:hover { + color: #1967be; +} +.text-success { + color: #ffffff; +} +a.text-success:hover { + color: #e6e6e6; +} +.text-info { + color: #ffffff; +} +a.text-info:hover { + color: #e6e6e6; +} +.text-warning { + color: #ffffff; +} +a.text-warning:hover { + color: #e6e6e6; +} +.text-danger { + color: #ffffff; +} +a.text-danger:hover { + color: #e6e6e6; +} +.bg-primary { + color: #fff; + background-color: #2780e3; +} +a.bg-primary:hover { + background-color: #1967be; +} +.bg-success { + background-color: #3fb618; +} +a.bg-success:hover { + background-color: #2f8912; +} +.bg-info { + background-color: #9954bb; +} +a.bg-info:hover { + background-color: #7e3f9d; +} +.bg-warning { + background-color: #ff7518; +} +a.bg-warning:hover { + background-color: #e45c00; +} +.bg-danger { + background-color: #ff0039; +} +a.bg-danger:hover { + background-color: #cc002e; +} +.page-header { + padding-bottom: 9.5px; + margin: 42px 0 21px; + border-bottom: 1px solid #e6e6e6; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10.5px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + list-style: none; + margin-left: -5px; +} +.list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} +dl { + margin-top: 0; + margin-bottom: 21px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10.5px 21px; + margin: 0 0 21px; + font-size: 18.75px; + border-left: 5px solid #e6e6e6; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #999999; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #e6e6e6; + border-left: 0; + text-align: right; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 21px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 0; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #ffffff; + background-color: #333333; + border-radius: 0; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; +} +pre { + display: block; + padding: 10px; + margin: 0 0 10.5px; + font-size: 14px; + line-height: 1.42857143; + word-break: break-all; + word-wrap: break-word; + color: #333333; + background-color: #f5f5f5; + border: 1px solid #cccccc; + border-radius: 0; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; +} +.row { + margin-left: -15px; + margin-right: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0%; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0%; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0%; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #999999; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 21px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #dddddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #dddddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #dddddd; +} +.table .table { + background-color: #ffffff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #dddddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + float: none; + display: table-column; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + float: none; + display: table-cell; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #3fb618; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #379f15; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #9954bb; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #8d46b0; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #ff7518; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #fe6600; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #ff0039; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #e60033; +} +.table-responsive { + overflow-x: auto; + min-height: 0.01%; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15.75px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #dddddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + padding: 0; + margin: 0; + border: 0; + min-width: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 21px; + font-size: 22.5px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 11px; + font-size: 15px; + line-height: 1.42857143; + color: #333333; +} +.form-control { + display: block; + width: 100%; + height: 43px; + padding: 10px 18px; + font-size: 15px; + line-height: 1.42857143; + color: #333333; + background-color: #ffffff; + background-image: none; + border: 1px solid #cccccc; + border-radius: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.form-control::-moz-placeholder { + color: #999999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999999; +} +.form-control::-webkit-input-placeholder { + color: #999999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #e6e6e6; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"], + input[type="time"], + input[type="datetime-local"], + input[type="month"] { + line-height: 43px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 31px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 64px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 21px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 11px; + padding-bottom: 11px; + margin-bottom: 0; + min-height: 36px; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-left: 0; + padding-right: 0; +} +.input-sm { + height: 31px; + padding: 5px 10px; + font-size: 13px; + line-height: 1.5; + border-radius: 0; +} +select.input-sm { + height: 31px; + line-height: 31px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 31px; + padding: 5px 10px; + font-size: 13px; + line-height: 1.5; + border-radius: 0; +} +select.form-group-sm .form-control { + height: 31px; + line-height: 31px; +} +textarea.form-group-sm .form-control, +select[multiple].form-group-sm .form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 31px; + padding: 5px 10px; + font-size: 13px; + line-height: 1.5; + min-height: 34px; +} +.input-lg { + height: 64px; + padding: 18px 30px; + font-size: 19px; + line-height: 1.3333333; + border-radius: 0; +} +select.input-lg { + height: 64px; + line-height: 64px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 64px; + padding: 18px 30px; + font-size: 19px; + line-height: 1.3333333; + border-radius: 0; +} +select.form-group-lg .form-control { + height: 64px; + line-height: 64px; +} +textarea.form-group-lg .form-control, +select[multiple].form-group-lg .form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 64px; + padding: 18px 30px; + font-size: 19px; + line-height: 1.3333333; + min-height: 40px; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 53.75px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 43px; + height: 43px; + line-height: 43px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback { + width: 64px; + height: 64px; + line-height: 64px; +} +.input-sm + .form-control-feedback { + width: 31px; + height: 31px; + line-height: 31px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #ffffff; +} +.has-success .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-success .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-success .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #3fb618; +} +.has-success .form-control-feedback { + color: #ffffff; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #ffffff; +} +.has-warning .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-warning .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-warning .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #ff7518; +} +.has-warning .form-control-feedback { + color: #ffffff; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #ffffff; +} +.has-error .form-control { + border-color: #ffffff; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} +.has-error .form-control:focus { + border-color: #e6e6e6; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ffffff; +} +.has-error .input-group-addon { + color: #ffffff; + border-color: #ffffff; + background-color: #ff0039; +} +.has-error .form-control-feedback { + color: #ffffff; +} +.has-feedback label ~ .form-control-feedback { + top: 26px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 11px; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 32px; +} +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 11px; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 24.9999994px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + } +} +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 10px 18px; + font-size: 15px; + line-height: 1.42857143; + border-radius: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #ffffff; + text-decoration: none; +} +.btn:active, +.btn.active { + outline: 0; + background-image: none; + -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.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + pointer-events: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-default { + color: #ffffff; + background-color: #222222; + border-color: #222222; +} +.btn-default:hover, +.btn-default:focus, +.btn-default.focus, +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #ffffff; + background-color: #090909; + border-color: #040404; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #222222; + border-color: #222222; +} +.btn-default .badge { + color: #222222; + background-color: #ffffff; +} +.btn-primary { + color: #ffffff; + background-color: #2780e3; + border-color: #2780e3; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary.focus, +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #ffffff; + background-color: #1967be; + border-color: #1862b5; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #2780e3; + border-color: #2780e3; +} +.btn-primary .badge { + color: #2780e3; + background-color: #ffffff; +} +.btn-success { + color: #ffffff; + background-color: #3fb618; + border-color: #3fb618; +} +.btn-success:hover, +.btn-success:focus, +.btn-success.focus, +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #ffffff; + background-color: #2f8912; + border-color: #2c8011; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #3fb618; + border-color: #3fb618; +} +.btn-success .badge { + color: #3fb618; + background-color: #ffffff; +} +.btn-info { + color: #ffffff; + background-color: #9954bb; + border-color: #9954bb; +} +.btn-info:hover, +.btn-info:focus, +.btn-info.focus, +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #ffffff; + background-color: #7e3f9d; + border-color: #783c96; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #9954bb; + border-color: #9954bb; +} +.btn-info .badge { + color: #9954bb; + background-color: #ffffff; +} +.btn-warning { + color: #ffffff; + background-color: #ff7518; + border-color: #ff7518; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning.focus, +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #ffffff; + background-color: #e45c00; + border-color: #da5800; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #ff7518; + border-color: #ff7518; +} +.btn-warning .badge { + color: #ff7518; + background-color: #ffffff; +} +.btn-danger { + color: #ffffff; + background-color: #ff0039; + border-color: #ff0039; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger.focus, +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #ffffff; + background-color: #cc002e; + border-color: #c2002b; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #ff0039; + border-color: #ff0039; +} +.btn-danger .badge { + color: #ff0039; + background-color: #ffffff; +} +.btn-link { + color: #2780e3; + font-weight: normal; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #165ba8; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999999; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 18px 30px; + font-size: 19px; + line-height: 1.3333333; + border-radius: 0; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 13px; + line-height: 1.5; + border-radius: 0; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 13px; + line-height: 1.5; + border-radius: 0; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + -o-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-property: height, visibility; + transition-property: height, visibility; + -webkit-transition-duration: 0.35s; + transition-duration: 0.35s; + -webkit-transition-timing-function: ease; + transition-timing-function: ease; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 15px; + text-align: left; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9.5px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + text-decoration: none; + color: #ffffff; + background-color: #2780e3; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #2780e3; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + left: auto; + right: 0; +} +.dropdown-menu-left { + left: 0; + right: auto; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 13px; + line-height: 1.42857143; + color: #999999; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px solid; + content: ""; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + left: auto; + right: 0; + } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} +.btn-group.open .dropdown-toggle { + -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-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-bottom-left-radius: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + float: none; + display: table-cell; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 64px; + padding: 18px 30px; + font-size: 19px; + line-height: 1.3333333; + border-radius: 0; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 64px; + line-height: 64px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 31px; + padding: 5px 10px; + font-size: 13px; + line-height: 1.5; + border-radius: 0; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 31px; + line-height: 31px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 10px 18px; + font-size: 15px; + font-weight: normal; + line-height: 1; + color: #333333; + text-align: center; + background-color: #e6e6e6; + border: 1px solid #cccccc; + border-radius: 0; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 13px; + border-radius: 0; +} +.input-group-addon.input-lg { + padding: 18px 30px; + font-size: 19px; + border-radius: 0; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-left: -1px; +} +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #e6e6e6; +} +.nav > li.disabled > a { + color: #999999; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999999; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #e6e6e6; + border-color: #2780e3; +} +.nav .nav-divider { + height: 1px; + margin: 9.5px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #dddddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 0 0 0 0; +} +.nav-tabs > li > a:hover { + border-color: #e6e6e6 #e6e6e6 #dddddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555555; + background-color: #ffffff; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 0; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 0 0 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 0; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #ffffff; + background-color: #2780e3; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + text-align: center; + margin-bottom: 5px; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 0; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #dddddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #dddddd; + border-radius: 0 0 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #ffffff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 21px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 0; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + padding: 14.5px 15px; + font-size: 19px; + line-height: 21px; + height: 50px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 0; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.25px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 21px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 21px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 14.5px; + padding-bottom: 14.5px; + } +} +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 3.5px; + margin-bottom: 3.5px; +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 3.5px; + margin-bottom: 3.5px; +} +.navbar-btn.btn-sm { + margin-top: 9.5px; + margin-bottom: 9.5px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 14.5px; + margin-bottom: 14.5px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #222222; + border-color: #121212; +} +.navbar-default .navbar-brand { + color: #ffffff; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #ffffff; + background-color: none; +} +.navbar-default .navbar-text { + color: #ffffff; +} +.navbar-default .navbar-nav > li > a { + color: #ffffff; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #ffffff; + background-color: #090909; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #090909; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #cccccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: transparent; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #090909; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #121212; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + background-color: #090909; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #ffffff; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #090909; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #090909; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #cccccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #ffffff; +} +.navbar-default .navbar-link:hover { + color: #ffffff; +} +.navbar-default .btn-link { + color: #ffffff; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #ffffff; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #cccccc; +} +.navbar-inverse { + background-color: #2780e3; + border-color: #1967be; +} +.navbar-inverse .navbar-brand { + color: #ffffff; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #ffffff; + background-color: none; +} +.navbar-inverse .navbar-text { + color: #ffffff; +} +.navbar-inverse .navbar-nav > li > a { + color: #ffffff; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #ffffff; + background-color: #1967be; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #ffffff; + background-color: #1967be; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #ffffff; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: transparent; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #1967be; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #ffffff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #1a6ecc; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + background-color: #1967be; + color: #ffffff; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #1967be; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #1967be; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #ffffff; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #ffffff; + background-color: #1967be; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #ffffff; + background-color: #1967be; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ffffff; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #ffffff; +} +.navbar-inverse .navbar-link:hover { + color: #ffffff; +} +.navbar-inverse .btn-link { + color: #ffffff; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #ffffff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #ffffff; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 21px; + list-style: none; + background-color: #f5f5f5; + border-radius: 0; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #cccccc; +} +.breadcrumb > .active { + color: #999999; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 21px 0; + border-radius: 0; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 10px 18px; + line-height: 1.42857143; + text-decoration: none; + color: #2780e3; + background-color: #ffffff; + border: 1px solid #dddddd; + margin-left: -1px; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: #165ba8; + background-color: #e6e6e6; + border-color: #dddddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #999999; + background-color: #f5f5f5; + border-color: #dddddd; + cursor: default; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999999; + background-color: #ffffff; + border-color: #dddddd; + cursor: not-allowed; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 18px 30px; + font-size: 19px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 13px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-bottom-right-radius: 0; + border-top-right-radius: 0; +} +.pager { + padding-left: 0; + margin: 21px 0; + list-style: none; + text-align: center; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 0; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #e6e6e6; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999999; + background-color: #ffffff; + cursor: not-allowed; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #222222; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #090909; +} +.label-primary { + background-color: #2780e3; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #1967be; +} +.label-success { + background-color: #3fb618; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #2f8912; +} +.label-info { + background-color: #9954bb; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #7e3f9d; +} +.label-warning { + background-color: #ff7518; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #e45c00; +} +.label-danger { + background-color: #ff0039; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #cc002e; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 13px; + font-weight: bold; + color: #ffffff; + line-height: 1; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: #2780e3; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #2780e3; + background-color: #ffffff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px 15px; + margin-bottom: 30px; + color: inherit; + background-color: #e6e6e6; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 23px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #cccccc; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 0; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding: 48px 0; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-left: 60px; + padding-right: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 67.5px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 21px; + line-height: 1.42857143; + background-color: #ffffff; + border: 1px solid #dddddd; + border-radius: 0; + -webkit-transition: border 0.2s ease-in-out; + -o-transition: border 0.2s ease-in-out; + transition: border 0.2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-left: auto; + margin-right: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #2780e3; +} +.thumbnail .caption { + padding: 9px; + color: #333333; +} +.alert { + padding: 15px; + margin-bottom: 21px; + border: 1px solid transparent; + border-radius: 0; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + background-color: #3fb618; + border-color: #4e9f15; + color: #ffffff; +} +.alert-success hr { + border-top-color: #438912; +} +.alert-success .alert-link { + color: #e6e6e6; +} +.alert-info { + background-color: #9954bb; + border-color: #7643a8; + color: #ffffff; +} +.alert-info hr { + border-top-color: #693c96; +} +.alert-info .alert-link { + color: #e6e6e6; +} +.alert-warning { + background-color: #ff7518; + border-color: #ff4309; + color: #ffffff; +} +.alert-warning hr { + border-top-color: #ee3800; +} +.alert-warning .alert-link { + color: #e6e6e6; +} +.alert-danger { + background-color: #ff0039; + border-color: #f0005e; + color: #ffffff; +} +.alert-danger hr { + border-top-color: #d60054; +} +.alert-danger .alert-link { + color: #e6e6e6; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + overflow: hidden; + height: 21px; + margin-bottom: 21px; + background-color: #cccccc; + border-radius: 0; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 13px; + line-height: 21px; + color: #ffffff; + text-align: center; + background-color: #2780e3; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + -webkit-transition: width 0.6s ease; + -o-transition: width 0.6s ease; + transition: width 0.6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #3fb618; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #9954bb; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #ff7518; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #ff0039; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + zoom: 1; + overflow: hidden; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + margin-bottom: 20px; + padding-left: 0; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #ffffff; + border: 1px solid #dddddd; +} +.list-group-item:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +a.list-group-item { + color: #555555; +} +a.list-group-item .list-group-item-heading { + color: #333333; +} +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + color: #555555; + background-color: #f5f5f5; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + background-color: #e6e6e6; + color: #999999; + cursor: not-allowed; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #999999; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #ffffff; + background-color: #2780e3; + border-color: #dddddd; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #dceafa; +} +.list-group-item-success { + color: #ffffff; + background-color: #3fb618; +} +a.list-group-item-success { + color: #ffffff; +} +a.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #ffffff; + background-color: #379f15; +} +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-info { + color: #ffffff; + background-color: #9954bb; +} +a.list-group-item-info { + color: #ffffff; +} +a.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #ffffff; + background-color: #8d46b0; +} +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-warning { + color: #ffffff; + background-color: #ff7518; +} +a.list-group-item-warning { + color: #ffffff; +} +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #ffffff; + background-color: #fe6600; +} +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-danger { + color: #ffffff; + background-color: #ff0039; +} +a.list-group-item-danger { + color: #ffffff; +} +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #ffffff; + background-color: #e60033; +} +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #ffffff; + border-color: #ffffff; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 21px; + background-color: #ffffff; + border: 1px solid transparent; + border-radius: 0; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: -1; + border-top-left-radius: -1; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 17px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #dddddd; + border-bottom-right-radius: -1; + border-bottom-left-radius: -1; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: -1; + border-top-left-radius: -1; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: -1; + border-bottom-left-radius: -1; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-left: 15px; + padding-right: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: -1; + border-top-left-radius: -1; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: -1; + border-top-right-radius: -1; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: -1; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: -1; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: -1; + border-bottom-left-radius: -1; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-left-radius: -1; + border-bottom-right-radius: -1; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: -1; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: -1; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #dddddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + border: 0; + margin-bottom: 0; +} +.panel-group { + margin-bottom: 21px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 0; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #dddddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #dddddd; +} +.panel-default { + border-color: #dddddd; +} +.panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #dddddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #dddddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #dddddd; +} +.panel-primary { + border-color: #2780e3; +} +.panel-primary > .panel-heading { + color: #ffffff; + background-color: #2780e3; + border-color: #2780e3; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #2780e3; +} +.panel-primary > .panel-heading .badge { + color: #2780e3; + background-color: #ffffff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #2780e3; +} +.panel-success { + border-color: #4e9f15; +} +.panel-success > .panel-heading { + color: #ffffff; + background-color: #3fb618; + border-color: #4e9f15; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #4e9f15; +} +.panel-success > .panel-heading .badge { + color: #3fb618; + background-color: #ffffff; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #4e9f15; +} +.panel-info { + border-color: #7643a8; +} +.panel-info > .panel-heading { + color: #ffffff; + background-color: #9954bb; + border-color: #7643a8; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #7643a8; +} +.panel-info > .panel-heading .badge { + color: #9954bb; + background-color: #ffffff; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #7643a8; +} +.panel-warning { + border-color: #ff4309; +} +.panel-warning > .panel-heading { + color: #ffffff; + background-color: #ff7518; + border-color: #ff4309; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ff4309; +} +.panel-warning > .panel-heading .badge { + color: #ff7518; + background-color: #ffffff; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ff4309; +} +.panel-danger { + border-color: #f0005e; +} +.panel-danger > .panel-heading { + color: #ffffff; + background-color: #ff0039; + border-color: #f0005e; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #f0005e; +} +.panel-danger > .panel-heading .badge { + color: #ff0039; + background-color: #ffffff; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #f0005e; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); +} +.well-lg { + padding: 24px; + border-radius: 0; +} +.well-sm { + padding: 9px; + border-radius: 0; +} +.close { + float: right; + font-size: 22.5px; + font-weight: bold; + line-height: 1; + color: #ffffff; + text-shadow: 0 1px 0 #ffffff; + opacity: 0.2; + filter: alpha(opacity=20); +} +.close:hover, +.close:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); +} +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} +.modal-open { + overflow: hidden; +} +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + transition: transform 0.3s ease-out; +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #ffffff; + border: 1px solid #999999; + border: 1px solid transparent; + border-radius: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; + outline: 0; +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000000; +} +.modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); +} +.modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); +} +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; + min-height: 16.42857143px; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 20px; +} +.modal-footer { + padding: 20px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Source Sans Pro", Calibri, Candara, Arial, sans-serif; + font-size: 13px; + font-weight: normal; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); +} +.tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); +} +.tooltip.top { + margin-top: -3px; + padding: 5px 0; +} +.tooltip.right { + margin-left: 3px; + padding: 0 5px; +} +.tooltip.bottom { + margin-top: 3px; + padding: 5px 0; +} +.tooltip.left { + margin-left: -3px; + padding: 0 5px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #ffffff; + text-align: center; + text-decoration: none; + background-color: #000000; + border-radius: 0; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + right: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Source Sans Pro", Calibri, Candara, Arial, sans-serif; + font-size: 15px; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + background-color: #ffffff; + background-clip: padding-box; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + white-space: normal; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 15px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: -1 -1 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + border-width: 10px; + content: ""; +} +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.25); + bottom: -11px; +} +.popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #ffffff; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.25); +} +.popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #ffffff; +} +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.25); + top: -11px; +} +.popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #ffffff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.25); +} +.popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #ffffff; + bottom: -10px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; +} +.carousel-inner > .item { + display: none; + position: relative; + -webkit-transition: 0.6s ease-in-out left; + -o-transition: 0.6s ease-in-out left; + transition: 0.6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform 0.6s ease-in-out; + -moz-transition: -moz-transform 0.6s ease-in-out; + -o-transition: -o-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000; + -moz-perspective: 1000; + perspective: 1000; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + left: 0; + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + left: 0; + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + left: 0; + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); +} +.carousel-control.right { + left: auto; + right: 0; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); +} +.carousel-control:hover, +.carousel-control:focus { + outline: 0; + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + line-height: 1; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #ffffff; + border-radius: 10px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); +} +.carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #ffffff; +} +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #ffffff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + content: " "; + display: table; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} + +/*# sourceMappingURL=01-vendor.css.map */ \ No newline at end of file diff --git a/doc-src/_layout.html b/doc-src/_layout.html index 871e3ad7..0a12e25e 100644 --- a/doc-src/_layout.html +++ b/doc-src/_layout.html @@ -12,7 +12,12 @@ - mitmproxy $!VERSION!$ docs + + + + + diff --git a/doc-src/mitmproxy-long.png b/doc-src/mitmproxy-long.png new file mode 100644 index 00000000..f9397d1e Binary files /dev/null and b/doc-src/mitmproxy-long.png differ -- cgit v1.2.3 From b91999507e662cbcce6c7968647d8cbac54fbcb5 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sat, 23 May 2015 12:36:52 +1200 Subject: Move websitelayout to website. --- doc-src/_websitelayout.html | 56 --------------------------------------------- doc-src/index.py | 3 +-- 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 doc-src/_websitelayout.html diff --git a/doc-src/_websitelayout.html b/doc-src/_websitelayout.html deleted file mode 100644 index 5a7c01bb..00000000 --- a/doc-src/_websitelayout.html +++ /dev/null @@ -1,56 +0,0 @@ - - -
-
-
- $!navbar!$ -
-
- - $!body!$ -
-
-
- -
-
-
-

Copyright 2014 Aldo Cortesi

-
-
- diff --git a/doc-src/index.py b/doc-src/index.py index 52b9c31a..b049e3ce 100644 --- a/doc-src/index.py +++ b/doc-src/index.py @@ -15,10 +15,9 @@ ns.VERSION = version.VERSION if ns.options.website: ns.idxpath = "doc/index.html" - this.layout = countershape.Layout("_websitelayout.html") else: ns.idxpath = "index.html" - this.layout = countershape.Layout("_layout.html") +this.layout = countershape.Layout("_layout.html") ns.title = countershape.template.Template(None, "

@!this.title!@

") this.titlePrefix = "%s - " % version.NAMEVERSION -- cgit v1.2.3 From 0a812656617be313c6648918f9b4c52a2ddb59c4 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Sun, 24 May 2015 13:16:49 +1200 Subject: docs: adjust for countershape changes --- doc-src/_layout.html | 88 ++++++++++++++++++++++++++++------------------------ doc-src/index.py | 7 +++-- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/doc-src/_layout.html b/doc-src/_layout.html index 0a12e25e..221da466 100644 --- a/doc-src/_layout.html +++ b/doc-src/_layout.html @@ -1,46 +1,54 @@ - -