diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..25c1f0e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [amitshekhariitbhu] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index d1a486f..c6c3621 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,17 @@ +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Android Studio generated folders +captures/ +.externalNativeBuild + +# IntelliJ project files *.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries +.idea/ + +# Misc .DS_Store -/build -/captures -.externalNativeBuild -/.idea diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..27b7bb9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,94 @@ +Change Log +========== + +Version 1.0.6 *(2019-03-07)* +---------------------------- + +* Fix: Fix query error +* Fix: Fix DebugDb class not found error + + +Version 1.0.5 *(2019-02-18)* +---------------------------- + +* Reduce size by taking out encrypted database library as a separate module +* New: Add support for database delete +* Changed compile to implementation +* Fix: Minor bug fixes + + +Version 1.0.4 *(2018-06-23)* +---------------------------- + +* Fix: Fix issue of Room Database + + +Version 1.0.3 *(2018-02-12)* +---------------------------- + +* New: Add support for debugging inMemory Room Database +* Add example for Room Database + + +Version 1.0.2 *(2018-01-08)* +---------------------------- + +* New: Add SqlCipher support +* New: List table name in non case sensitive alphabetical order + + +Version 1.0.1 *(2017-06-23)* +---------------------------- + +* New: Add insert row feature +* New: Add custom database files support +* New: Add method for checking isServerRunning +* New: Add pragma support +* Fix: Minor bug fixes + + +Version 1.0.0 *(2017-02-08)* +---------------------------- + +* New: Add support for editing database directly +* New: Delete rows directly +* New: Delete Shared Pref +* New: Edit shared preferences directly +* New: Add standard code for checking databases files +* New: Complete offline support +* Refactor library code + + +Version 0.5.0 *(2017-01-21)* +---------------------------- + +* New: Export DB +* New: Method to get DB version +* Fix: Fix proguard issue and other minor issues + + +Version 0.4.0 *(2016-11-29)* +---------------------------- + +* Optimizations +* Fix: Fix few minor bugs + + +Version 0.3.0 *(2016-11-23)* +---------------------------- + +* New: Add support for custom port +* Fix: Fix few minor bugs + + +Version 0.2.0 *(2016-11-17)* +---------------------------- + +* New: Add method for getting address +* Fix: Fix few minor bugs + + +Version 0.1.0 *(2016-11-16)* +---------------------------- + +Initial release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3741d94 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contributing + +1. Fork it! +2. Checkout the development branch: `git checkout development` +3. Create your feature branch: `git checkout -b my-new-feature` +4. Add your changes to the index: `git add .` +5. Commit your changes: `git commit -m 'Add some feature'` +6. Push to the branch: `git push origin my-new-feature` +7. Submit a pull request against the `development` branch diff --git a/LICENSE b/LICENSE index 7a4a3ea..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -199,4 +199,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index 08e43fd..03487e1 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,12 @@ # Android Debug Database -[![Mindorks](https://img.shields.io/badge/mindorks-opensource-blue.svg)](https://mindorks.com/open-source-projects) -[![Mindorks Community](https://img.shields.io/badge/join-community-blue.svg)](https://mindorks.com/join-community) -[![Android Weekly](https://img.shields.io/badge/Android%20Weekly-%23233-blue.svg)](http://androidweekly.net/issues/issue-233) -[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android%20Debug%20Database-blue.svg?style=flat)](http://android-arsenal.com/details/1/4667) -[![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9) -[![Download](https://api.bintray.com/packages/amitshekhariitbhu/maven/debug-db/images/download.svg) ](https://bintray.com/amitshekhariitbhu/maven/debug-db/_latestVersion) -[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=102)](https://opensource.org/licenses/Apache-2.0) -[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/amitshekhariitbhu/Android-Debug-Database/blob/master/LICENSE) +## Android Debug Database is a powerful library for debugging databases and shared preferences in Android applications -## Android Debug Database is a powerful library for debugging databases and shared preferences in Android applications. - -### Android Debug Database allows you to view databases and shared preferences directly in your browser in a very simple way. +### Android Debug Database allows you to view databases and shared preferences directly in your browser in a very simple way ### What can Android Debug Database do? + * See all the databases. * See all the data in the shared preferences used in your application. * Run any sql query on the given database to update and delete your data. @@ -27,19 +19,66 @@ * Search in your data. * Sort data. * Download database. +* Debug Room inMemory database. -### All these features work without rooting your device -> No need of rooted device +## About me + +Hi, I am Amit Shekhar, Co-Founder @ [Outcome School](https://outcomeschool.com) • IIT 2010-14 • I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos. -### Check out another awesome library for fast and simple networking in Android. -* [Fast Android Networking Library](https://github.com/amitshekhariitbhu/Fast-Android-Networking) +You can connect with me on: + +- [Twitter](https://twitter.com/amitiitbhu) +- [YouTube](https://www.youtube.com/@amitshekhar) +- [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu) +- [GitHub](https://github.com/amitshekhariitbhu) + +## [Outcome School Blog](https://outcomeschool.com/blog) - High-quality content to learn Android concepts. + +### All these features work without rooting your device -> No need of rooted device ### Using Android Debug Database Library in your application -Add this to your app's build.gradle + +Add this in your `settings.gradle`: +```groovy +maven { url 'https://jitpack.io' } +``` + +If you are using `settings.gradle.kts`, add the following: +```kotlin +maven { setUrl("https://jitpack.io") } +``` + +Add this in your `build.gradle` ```groovy -debugCompile 'com.amitshekhar.android:debug-db:1.0.1' +debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.7' ``` -Use `debugCompile` so that it will only compile in your debug build and not in your release build. +If you are using `build.gradle.kts`, add the following: +```kotlin +debugImplementation("com.github.amitshekhariitbhu.Android-Debug-Database:debug-db:1.0.7") +``` + +Using the Android Debug Database with encrypted database + +Add this in your `build.gradle` +```groovy +debugImplementation 'com.github.amitshekhariitbhu.Android-Debug-Database:debug-db-encrypt:1.0.7' +``` + +If you are using `build.gradle.kts`, add the following: +```kotlin +debugImplementation("com.github.amitshekhariitbhu.Android-Debug-Database:debug-db-encrypt:1.0.7") +``` + +And to provide the password for the DB, you should add this in the Gradle: +DB_PASSWORD_{VARIABLE}, if for example, PERSON is the database name: DB_PASSWORD_PERSON +```groovy +debug { + resValue("string", "DB_PASSWORD_PERSON", "password") +} +``` + +Use `debugImplementation` so that it will only compile in your debug build and not in your release build. That’s all, just start the application, you will see in the logcat an entry like follows : @@ -49,11 +88,14 @@ That’s all, just start the application, you will see in the logcat an entry li Now open the provided link in your browser. +Tip: Use the "Customize UI" link next to "Run Query" to adjust table layout (stored only in your browser). + Important: -- Your Android phone and laptop should be connected to the same Network (Wifi or LAN). -- If you are using it over usb, run `adb forward tcp:8080 tcp:8080` -Note : If you want use different port other than 8080. +* Your Android phone and laptop should be connected to the same Network (Wifi or LAN). +* If you are using it over usb, run `adb forward tcp:8080 tcp:8080` + +Note : If you want use different port other than 8080. In the app build.gradle file under buildTypes do the following change ```groovy @@ -62,23 +104,25 @@ debug { } ``` - - - You will see something like this : ### Seeing values + ### Editing values + ### Working with emulator -- Android Default Emulator: Run the command in the terminal - `adb forward tcp:8080 tcp:8080` and open http://localhost:8080 -- Genymotion Emulator: Enable bridge from configure virtual device (option available in genymotion) + +* Android Default Emulator: Run the command in the terminal - `adb forward tcp:8080 tcp:8080` and open http://localhost:8080 +* Genymotion Emulator: Enable bridge from configure virtual device (option available in genymotion) ### Getting address with toast, in case you missed the address log in logcat + As this library is auto-initialize, if you want to get the address log, add the following method and call (we have to do like this to avoid build error in release build as this library will not be included in the release build) using reflection. + ```java public static void showDebugDBAddressLogToast(Context context) { if (BuildConfig.DEBUG) { @@ -95,7 +139,9 @@ public static void showDebugDBAddressLogToast(Context context) { ``` ### Adding custom database files -As this library is auto-initialize, if you want to add custom database files, add the following method and call + +As this library is auto-initialize, if you want to debug custom database files, add the following method and call + ```java public static void setCustomDatabaseFiles(Context context) { if (BuildConfig.DEBUG) { @@ -103,11 +149,11 @@ public static void setCustomDatabaseFiles(Context context) { Class debugDB = Class.forName("com.amitshekhar.DebugDB"); Class[] argTypes = new Class[]{HashMap.class}; Method setCustomDatabaseFiles = debugDB.getMethod("setCustomDatabaseFiles", argTypes); - HashMap customDatabaseFiles = new HashMap<>(); + HashMap> customDatabaseFiles = new HashMap<>(); // set your custom database files customDatabaseFiles.put(ExtTestDBHelper.DATABASE_NAME, - new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME + - "/" + ExtTestDBHelper.DATABASE_NAME)); + new Pair<>(new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME + + "/" + ExtTestDBHelper.DATABASE_NAME), "")); setCustomDatabaseFiles.invoke(null, customDatabaseFiles); } catch (Exception ignore) { @@ -116,25 +162,50 @@ public static void setCustomDatabaseFiles(Context context) { } ``` +### Adding InMemory Room databases + +As this library is auto-initialize, if you want to debug inMemory Room databases, add the following method and call + +```java +public static void setInMemoryRoomDatabases(SupportSQLiteDatabase... database) { + if (BuildConfig.DEBUG) { + try { + Class debugDB = Class.forName("com.amitshekhar.DebugDB"); + Class[] argTypes = new Class[]{HashMap.class}; + HashMap inMemoryDatabases = new HashMap<>(); + // set your inMemory databases + inMemoryDatabases.put("InMemoryOne.db", database[0]); + Method setRoomInMemoryDatabase = debugDB.getMethod("setInMemoryRoomDatabases", argTypes); + setRoomInMemoryDatabase.invoke(null, inMemoryDatabases); + } catch (Exception ignore) { + + } + } +} +``` + ### Find this project useful ? :heart: + * Support it by clicking the :star: button on the upper right of this page. :v: ### TODO + * Simplify emulator issue [Issue Link](https://github.com/amitshekhariitbhu/Android-Debug-Database/issues/6) * And of course many more features and bug fixes. -### [Check out Mindorks awesome open source projects here](https://mindorks.com/open-source-projects) +You can connect with me on: -### Contact - Let's become friends - [Twitter](https://twitter.com/amitiitbhu) -- [Github](https://github.com/amitshekhariitbhu) -- [Medium](https://medium.com/@amitshekhar) +- [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu) +- [GitHub](https://github.com/amitshekhariitbhu) - [Facebook](https://www.facebook.com/amit.shekhar.iitbhu) +[**Read all of our blogs here.**](https://outcomeschool.com/blog) + ### License + ``` - Copyright (C) 2016 Amit Shekhar - Copyright (C) 2011 Android Open Source Project + Copyright (C) 2024 Amit Shekhar Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -150,4 +221,6 @@ public static void setCustomDatabaseFiles(Context context) { ``` ### Contributing to Android Debug Database -Just make pull request. You're in! + +All pull requests are welcome, make sure to follow the [contribution guidelines](CONTRIBUTING.md) +when you submit pull request. diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index 6b7a2bf..0000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,17 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/amitshekhar/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/build.gradle b/build.gradle index 43bc990..4029db9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,27 +18,16 @@ */ // Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - jcenter() - } +plugins { + id 'com.android.application' version '7.3.0' apply false + id 'com.android.library' version '7.3.0' apply false } task clean(type: Delete) { delete rootProject.buildDir } +ext { + compileSdk = 33 + targetSdk = 33 + minSdk = 14 +} \ No newline at end of file diff --git a/debug-db-base/build.gradle b/debug-db-base/build.gradle new file mode 100644 index 0000000..2cca330 --- /dev/null +++ b/debug-db-base/build.gradle @@ -0,0 +1,46 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +apply plugin: 'com.android.library' + +android { + compileSdk rootProject.ext.compileSdk + defaultConfig { + minSdk rootProject.ext.minSdk + targetSdk rootProject.ext.targetSdk + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + resValue("string", "PORT_NUMBER", "8080") + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation 'com.google.code.gson:gson:2.8.5' + implementation "androidx.room:room-runtime:2.5.0" + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/debug-db-base/proguard-rules.pro b/debug-db-base/proguard-rules.pro new file mode 100644 index 0000000..0177867 --- /dev/null +++ b/debug-db-base/proguard-rules.pro @@ -0,0 +1,78 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +-renamesourcefileattribute SourceFile + +-keepparameternames +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,EnclosingMethod + +# Preserve all annotations. + +-keepattributes *Annotation* + +# Preserve all public classes, and their public and protected fields and +# methods. + +-keep public class * { + public protected *; +} + +# Preserve all .class method names. + +-keepclassmembernames class * { + java.lang.Class class$(java.lang.String); + java.lang.Class class$(java.lang.String, boolean); +} + +# Preserve all native method names and the names of their classes. + +-keepclasseswithmembernames class * { + native ; +} + +# Preserve the special static methods that are required in all enumeration +# classes. + +-keepclassmembers class * extends java.lang.Enum { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +# Explicitly preserve all serialization members. The Serializable interface +# is only a marker interface, so it wouldn't save them. +# You can comment this out if your library doesn't use serialization. +# If your code contains serializable classes that have to be backward +# compatible, please refer to the manual. + +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + static final java.io.ObjectStreamField[] serialPersistentFields; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + +# Your library may contain more items that need to be preserved; +# typically classes that are dynamically created using Class.forName: + +# -keep public class mypackage.MyClass +# -keep public interface mypackage.MyInterface +# -keep public class * implements mypackage.MyInterface diff --git a/debug-db/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java b/debug-db-base/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java similarity index 97% rename from debug-db/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java rename to debug-db-base/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java index cccaafe..9716f44 100644 --- a/debug-db/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java +++ b/debug-db-base/src/androidTest/java/com/amitshekhar/ExampleInstrumentedTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db-base/src/main/AndroidManifest.xml b/debug-db-base/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c168ab5 --- /dev/null +++ b/debug-db-base/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/debug-db/src/main/assets/app.js b/debug-db-base/src/main/assets/app.js similarity index 52% rename from debug-db/src/main/assets/app.js rename to debug-db-base/src/main/assets/app.js index f8329af..a990219 100644 --- a/debug-db/src/main/assets/app.js +++ b/debug-db-base/src/main/assets/app.js @@ -1,5 +1,12 @@ $( document ).ready(function() { - getDBList(); + if (window.AddbUiCustomization && typeof window.AddbUiCustomization.init === "function") { + window.AddbUiCustomization.init(); + } + if (shouldUseDemoData()) { + initDemoMode(); + } else { + getDBList(); + } $("#query").keypress(function(e){ if(e.which == 13) { queryFunction(); @@ -21,10 +28,230 @@ $( document ).ready(function() { $(this).addClass('selected'); }); + $( document ).on( "click", "#addb-query-history-toggle", function() { + renderQueryHistoryMenu(); + }); + + $( document ).on( "click", ".addb-query-history-item", function(e) { + e.preventDefault(); + var query = $(this).data("query"); + if (query != null) { + $("#query").val(query).focus(); + } + }); }); var isDatabaseSelected = true; +var isDemoModeEnabled = false; + +var QUERY_HISTORY_STORAGE_KEY = "addb.queryHistory.v1"; +var QUERY_HISTORY_MAX_ITEMS = 10; + +function loadQueryHistory() { + try { + if (!window.localStorage) return []; + var raw = window.localStorage.getItem(QUERY_HISTORY_STORAGE_KEY); + if (!raw) return []; + var parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed.filter(function (item) { return typeof item === "string" && item.trim().length > 0; }); + } catch (e) { + return []; + } +} + +function saveQueryHistory(history) { + try { + if (!window.localStorage) return; + window.localStorage.setItem(QUERY_HISTORY_STORAGE_KEY, JSON.stringify(history)); + } catch (e) { + // ignore (e.g. storage disabled) + } +} + +function addQueryToHistory(query) { + var normalizedQuery = (query == null ? "" : String(query)).trim(); + if (!normalizedQuery) return; + + var history = loadQueryHistory(); + + for (var i = history.length - 1; i >= 0; i--) { + if (history[i] === normalizedQuery) { + history.splice(i, 1); + } + } + + history.unshift(normalizedQuery); + if (history.length > QUERY_HISTORY_MAX_ITEMS) { + history = history.slice(0, QUERY_HISTORY_MAX_ITEMS); + } + saveQueryHistory(history); +} + +function renderQueryHistoryMenu() { + var menu = $("#addb-query-history-menu"); + if (!menu.length) return; + + menu.empty(); + + var history = loadQueryHistory(); + if (!history.length) { + menu.append($("
  • ", { "class": "disabled" }).append($("", { href: "#", tabindex: -1 }).text("No recent queries"))); + return; + } + + for (var i = 0; i < history.length; i++) { + menu.append( + $("
  • ").append( + $("", { href: "#", "class": "addb-query-history-item", title: history[i] }).text(history[i]).data("query", history[i]) + ) + ); + } +} + +function shouldUseDemoData() { + try { + var search = window.location.search || ""; + if (/(^|[?&])demo=1(&|$)/.test(search)) return true; + return window.location.protocol === "file:"; + } catch (e) { + return false; + } +} + +function demoRepeat(str, count) { + var out = ""; + for (var i = 0; i < count; i++) out += str; + return out; +} + +function demoCell(value, dataType) { + return { value: value, dataType: dataType }; +} + +function buildDemoResult() { + var longToken = "tok_" + demoRepeat("A", 72) + "." + demoRepeat("B", 72) + "." + demoRepeat("C", 72); + var veryLongUrl = + "https://example.com/" + + demoRepeat("path/", 8) + + "resource?query=" + + demoRepeat("x", 48) + + "&more=" + + demoRepeat("y", 48); + + var json1 = JSON.stringify({ + user: { + id: 42, + name: "Ada Lovelace", + flags: { isAdmin: false, isTester: true, beta: true } + }, + session: { + token: longToken, + expiresAt: "2025-12-14T12:34:56Z" + }, + features: ["ui_customization", "json_tree", "sticky_header"] + }); + + var json2 = JSON.stringify({ + request: { + method: "POST", + url: veryLongUrl, + headers: { + "content-type": "application/json", + "x-request-id": "req_" + demoRepeat("7", 24) + } + }, + timingsMs: { dns: 12, tcp: 34, tls: 56, ttfb: 78, total: 234 }, + tags: ["android", "debug-db", "datatable"] + }); + + var json3 = JSON.stringify([ + { type: "event", ts: 1734150000, data: { name: "launch", coldStart: true } }, + { type: "event", ts: 1734150030, data: { name: "tap", target: "settings_button" } }, + { type: "event", ts: 1734150055, data: { name: "network", status: 200, bytes: 2048 } } + ]); + + var json4 = JSON.stringify({ + emptyObject: {}, + emptyArray: [], + nested: { + level2: { + level3: { + message: "Expand/collapse should keep 2nd-level collapsed by default.", + numbers: [1, 2, 3, 4, 5] + } + } + } + }); + + return { + isSuccessful: true, + isSelectQuery: true, + isEditable: false, + tableInfos: [ + { title: "id", isPrimary: true }, + { title: "name", isPrimary: false }, + { title: "is_active", isPrimary: false }, + { title: "json_payload", isPrimary: false }, + { title: "notes", isPrimary: false } + ], + rows: [ + [ + demoCell(1, "number"), + demoCell("Alpha", "string"), + demoCell(true, "boolean"), + demoCell(json1, "string"), + demoCell("Try JSON mode: Pretty-printed", "string") + ], + [ + demoCell(2, "number"), + demoCell("Beta", "string"), + demoCell(false, "boolean"), + demoCell(json2, "string"), + demoCell("Includes a very long URL + token", "string") + ], + [ + demoCell(3, "number"), + demoCell("Gamma", "string"), + demoCell(true, "boolean"), + demoCell(json3, "string"), + demoCell("Root JSON array (children collapsed at level 2)", "string") + ], + [ + demoCell(4, "number"), + demoCell("Delta", "string"), + demoCell(true, "boolean"), + demoCell(json4, "string"), + demoCell("Has empty containers + deep nesting", "string") + ] + ] + }; +} + +function loadDemoData() { + var result = buildDemoResult(); + inflateData(result); +} + +function initDemoMode() { + isDemoModeEnabled = true; + isDatabaseSelected = false; + + $("#selected-db-info").text("Demo mode (local file) — showing sample data for UI testing"); + + $("#run-query").removeClass("active").addClass("disabled").prop("disabled", true); + $("#selected-db-download").removeClass("active").addClass("disabled").prop("disabled", true); + $("#selected-db-delete").removeClass("active").addClass("disabled").prop("disabled", true); + + $("#db-list").empty().append("DEMO_DB"); + $("#table-list") + .empty() + .append("demo_table"); + + loadDemoData(); + showSuccessInfo("Loaded demo data (open Customize UI → Text & JSON to try Pretty-printed)"); +} function getData(tableName) { @@ -41,6 +268,13 @@ function queryFunction() { var query = $('#query').val(); + if (isDemoModeEnabled) { + showErrorInfo("Demo mode: database queries are not available (UI only)"); + return; + } + + addQueryToHistory(query); + $.ajax({url: "query?query="+escape(query), success: function(result){ result = JSON.parse(result); @@ -58,6 +292,21 @@ function downloadDb() { } } +function deleteDb() { + if (isDatabaseSelected) { + $.ajax({url: "deleteDb", success: function(result){ + result = JSON.parse(result); + if(result.isSuccessful){ + console.log("Database deleted successfully"); + showSuccessInfo("Database Deleted Successfully"); + getDBList(); + } else { + console.log("Database delete failed"); + showErrorInfo("Database Delete Failed"); + } + }}); + } +} function getDBList() { @@ -68,11 +317,15 @@ function getDBList() { $('#db-list').empty(); var isSelectionDone = false; for(var count = 0; count < dbList.length; count++){ - if(dbList[count].indexOf("journal") == -1){ - $("#db-list").append("" +dbList[count] + ""); + var dbName = dbList[count][0]; + var isEncrypted = dbList[count][1]; + var isDownloadable = dbList[count][2]; + var dbAttribute = isEncrypted == "true" ? ' ' : ""; + if(dbName.indexOf("journal") == -1 && dbName.indexOf("-wal") == -1 && dbName.indexOf("-shm") == -1){ + $("#db-list").append("" + dbName + dbAttribute + ""); if(!isSelectionDone){ isSelectionDone = true; - $('#db-list').find('a').trigger('click'); + $('#db-list').find('a').trigger('click'); } } } @@ -81,22 +334,34 @@ function getDBList() { } -function openDatabaseAndGetTableList(db) { +var lastTableName = getHashValue('table'); +function openDatabaseAndGetTableList(db, isDownloadable) { if("APP_SHARED_PREFERENCES" == db) { $('#run-query').removeClass('active'); $('#run-query').addClass('disabled'); - $('#selected-db-info').removeClass('active'); - $('#selected-db-info').addClass('disabled'); + $('#selected-db-download').removeClass('active'); + $('#selected-db-delete').removeClass('active'); + $('#selected-db-download').addClass('disabled'); + $('#selected-db-delete').addClass('disabled'); isDatabaseSelected = false; $("#selected-db-info").text("SharedPreferences"); } else { $('#run-query').removeClass('disabled'); $('#run-query').addClass('active'); - $('#selected-db-info').removeClass('disabled'); - $('#selected-db-info').addClass('active'); + $("#selected-db-info").text("Selected Database : "+db); + if("true" == isDownloadable) { + $('#selected-db-download').addClass('active'); + $('#selected-db-delete').addClass('active'); + $('#selected-db-download').removeClass('disabled'); + $('#selected-db-delete').removeClass('disabled'); + } else { + $('#selected-db-download').removeClass('active'); + $('#selected-db-delete').removeClass('active'); + $('#selected-db-download').addClass('disabled'); + $('#selected-db-delete').addClass('disabled'); + } isDatabaseSelected = true; - $("#selected-db-info").text("Export Selected Database : "+db); } @@ -106,14 +371,18 @@ function openDatabaseAndGetTableList(db) { var tableList = result.rows; var dbVersion = result.dbVersion; if("APP_SHARED_PREFERENCES" != db) { - $("#selected-db-info").text("Export Selected Database : "+db +" Version : "+dbVersion); + $("#selected-db-info").text("Selected Database : "+db +" Version : "+dbVersion); } $('#table-list').empty() for(var count = 0; count < tableList.length; count++){ - var tableName = tableList[count]; - $("#table-list").append("" +tableName + ""); - } - + var tableName = tableList[count]; + $("#table-list").append("" + tableName + ""); + } + + if (lastTableName !== null) { + $('a[data-table-name=' + lastTableName + ']').trigger('click'); + } }}); } @@ -171,6 +440,11 @@ function inflateData(result){ availableButtons = []; } + var displayLength = 10; + if (window.AddbUiCustomization && typeof window.AddbUiCustomization.getSettings === "function") { + displayLength = window.AddbUiCustomization.getSettings().pageLength; + } + $(tableId).dataTable({ "data": columnData, "columnDefs": columnHeader, @@ -179,14 +453,19 @@ function inflateData(result){ 'bFilter': true, 'bInfo': true, "bSort" : true, + "order": [], "scrollX": true, - "iDisplayLength": 10, + "iDisplayLength": displayLength, "dom": "Bfrtip", select: 'single', altEditor: true, // Enable altEditor buttons: availableButtons }) + if (window.AddbUiCustomization && typeof window.AddbUiCustomization.onDataTableCreated === "function") { + window.AddbUiCustomization.onDataTableCreated($(tableId).DataTable()); + } + //attach row-updated listener $(tableId).on('update-row.dt', function (e, updatedRowData, callback) { var updatedRowDataArray = JSON.parse(updatedRowData); @@ -389,3 +668,8 @@ function showErrorInfo(message){ snackbarElement.removeClass("show"); }, 3000); } + +function getHashValue(key) { + var matches = location.hash.match(new RegExp(key + '=([^&]*)')); + return matches ? matches[1] : null; +} diff --git a/debug-db/src/main/assets/bootstrap.min.css b/debug-db-base/src/main/assets/bootstrap.min.css similarity index 99% rename from debug-db/src/main/assets/bootstrap.min.css rename to debug-db-base/src/main/assets/bootstrap.min.css index ed3905e..82113bd 100644 --- a/debug-db/src/main/assets/bootstrap.min.css +++ b/debug-db-base/src/main/assets/bootstrap.min.css @@ -3,4 +3,4 @@ * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */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}.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:"\002a"}.glyphicon-plus:before{content:"\002b"}.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: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:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,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: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::-ms-expand{background-color:transparent;border:0}.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].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{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}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 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}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 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}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.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:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.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: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{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,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.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,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.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,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.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,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.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,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.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,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-top:4px solid\9;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 dashed;border-bottom:4px solid\9}.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,.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-left-radius:4px;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-right-radius:4px;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 .form-control:focus{z-index:3}.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{z-index:2;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{z-index:2;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:3;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;line-height:1.3333333}.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;line-height:1.5}.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:middle;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-top:30px;padding-bottom:30px;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{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.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-object.img-thumbnail{max-width:none}.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,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.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,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.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,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.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,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.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,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.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,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.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,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.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,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.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,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.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>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.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{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-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.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;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-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;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);line-break:auto}.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:1000px;perspective:1000px}.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);background-color:rgba(0,0,0,0);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;margin-top:-10px}.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;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:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.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,.modal-header:after,.modal-header: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,.modal-header: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!important}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!important}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!important}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!important}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!important}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}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file +/*# sourceMappingURL=bootstrap.min.css.map */ diff --git a/debug-db/src/main/assets/bootstrap.min.js b/debug-db-base/src/main/assets/bootstrap.min.js similarity index 99% rename from debug-db/src/main/assets/bootstrap.min.js rename to debug-db-base/src/main/assets/bootstrap.min.js index 9bcd2fc..be9574d 100644 --- a/debug-db/src/main/assets/bootstrap.min.js +++ b/debug-db-base/src/main/assets/bootstrap.min.js @@ -4,4 +4,4 @@ * Licensed under the MIT license */ if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file +this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); diff --git a/debug-db/src/main/assets/buttons.dataTables.min.css b/debug-db-base/src/main/assets/buttons.dataTables.min.css similarity index 100% rename from debug-db/src/main/assets/buttons.dataTables.min.css rename to debug-db-base/src/main/assets/buttons.dataTables.min.css diff --git a/debug-db-base/src/main/assets/custom.css b/debug-db-base/src/main/assets/custom.css new file mode 100644 index 0000000..f7761d9 --- /dev/null +++ b/debug-db-base/src/main/assets/custom.css @@ -0,0 +1,283 @@ +.padding-fifty { + padding-top: 50px; +} + +.padding-twenty { + padding-top: 20px; +} + +.display-none { + display: none; +} + +.list-group-item { + word-break: break-all; +} + +.list-group-item.selected { + background: #dff0d8 !important; + color: #3c763d !important; + font-weight: bold; +} + +#addb-customize-categories .list-group-item { + word-break: normal; +} + +body.addb-sidebar-compact #addb-db-column .panel-heading, +body.addb-sidebar-compact #addb-table-column .panel-heading { + padding: 6px 8px; + font-size: 12px; +} + +body.addb-sidebar-compact #db-list .list-group-item, +body.addb-sidebar-compact #table-list .list-group-item { + padding: 6px 8px; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + word-break: normal; +} + +.addb-customize-link { + margin-right: 8px; +} + +.addb-query-history { + display: inline-block; + margin-left: 6px; +} + +.addb-query-history-toggle { + padding: 0 4px; + color: #777; +} + +.addb-query-history-toggle:hover, +.addb-query-history-toggle:focus { + color: #333; + text-decoration: none; +} + +#addb-query-history-menu { + min-width: 30vw; + width: 40vw; + max-width: 70vw; + max-height: 160px; + overflow-y: auto; + overflow-x: hidden; + padding: 0; +} + +#addb-query-history-menu > li > a { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + box-sizing: border-box; + height: 32px; + line-height: 20px; + padding: 6px 12px; +} + +@font-face { + font-family: "AddbGlyphicons"; + src: url("fonts/glyphicons-halflings-regular.ttf") format("truetype"); + font-weight: normal; + font-style: normal; +} + +.glyphicon { + font-family: "AddbGlyphicons"; +} + +.addb-customize-heading .btn-link { + padding-top: 0; + padding-bottom: 0; +} + +.addb-customize-summary { + margin-bottom: 20px; + color: #555; +} + +.addb-max-width-input { + max-width: 240px; +} + +#app-container.addb-container-fluid-max { + max-width: 1600px; + margin-left: auto; + margin-right: auto; +} + +:root { + --addb-max-col-width: 480px; + --addb-sticky-header-top: 50px; + --addb-json-max-lines: 10; +} + +body.addb-sticky-header #parent-data-div .dataTables_scrollHead { + position: sticky; + top: var(--addb-sticky-header-top); + z-index: 20; + background: #fff; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08); +} + +body.addb-density-compact #parent-data-div table.dataTable thead th, +body.addb-density-compact #parent-data-div table.dataTable thead td, +body.addb-density-compact #parent-data-div table.dataTable tbody th, +body.addb-density-compact #parent-data-div table.dataTable tbody td { + padding: 4px 8px; + line-height: 1.2; +} + +body.addb-density-comfortable #parent-data-div table.dataTable thead th, +body.addb-density-comfortable #parent-data-div table.dataTable thead td, +body.addb-density-comfortable #parent-data-div table.dataTable tbody th, +body.addb-density-comfortable #parent-data-div table.dataTable tbody td { + padding: 12px 14px; + line-height: 1.4; +} + +body.addb-wrap-long-text #parent-data-div table.dataTable th, +body.addb-wrap-long-text #parent-data-div table.dataTable td { + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; +} + +body.addb-max-col-width #parent-data-div table.dataTable { + table-layout: fixed; +} + +body.addb-max-col-width #parent-data-div table.dataTable th, +body.addb-max-col-width #parent-data-div table.dataTable td { + max-width: var(--addb-max-col-width); +} + +body.addb-json-mode-wrapped #parent-data-div table.dataTable td.addb-json-cell { + white-space: normal; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + overflow-wrap: break-word; + word-break: normal; + vertical-align: top; +} + +body.addb-json-mode-wrapped #parent-data-div table.dataTable td.addb-json-cell .addb-json-text { + display: block; + line-height: 1.25; + max-height: calc(var(--addb-json-max-lines) * 1.25em); + overflow-y: auto; + padding-right: 6px; +} + +body.addb-json-mode-pretty #parent-data-div table.dataTable td.addb-json-cell { + white-space: pre; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + overflow-wrap: normal; + word-break: normal; + vertical-align: top; +} + +body.addb-max-col-width.addb-json-mode-pretty #parent-data-div table.dataTable td.addb-json-cell { + white-space: pre-wrap; + overflow-wrap: break-word; +} + +body.addb-json-mode-pretty #parent-data-div table.dataTable td.addb-json-cell .addb-json-tree { + display: inline-block; + line-height: 1.25; + max-height: calc(var(--addb-json-max-lines) * 1.25em); + overflow-y: auto; + overflow-x: visible; + padding-right: 6px; +} + +#parent-data-div table.dataTable td.addb-json-cell .addb-json-line { + display: block; + margin: 0; +} + +#parent-data-div table.dataTable td.addb-json-cell .addb-json-gutter { + display: inline-block; + width: 14px; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node { + margin: 0; + padding: 0; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node > summary { + cursor: pointer; + list-style: none; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node > summary::-webkit-details-marker { + display: none; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node > summary::marker { + content: ""; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node > summary .addb-json-gutter::before { + content: "▸"; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node[open] > summary .addb-json-gutter::before { + content: "▾"; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node > summary .addb-json-summary-open { + display: none; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node[open] > summary .addb-json-summary-open { + display: inline; +} + +#parent-data-div table.dataTable td.addb-json-cell details.addb-json-node[open] > summary .addb-json-summary-closed { + display: none; +} + +#snackbar { + visibility: hidden; + min-width: 250px; + margin-left: -125px; + background-color: #5cb85c; + color: #fff; + text-align: center; + border-radius: 2px; + padding: 16px; + position: fixed; + z-index: 1; + left: 50%; + bottom: 30px; + font-size: 17px; +} + +#snackbar.show { + visibility: visible; + -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; + animation: fadein 0.5s, fadeout 0.5s 2.5s; +} + +@-webkit-keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@keyframes fadein { + from {bottom: 0; opacity: 0;} + to {bottom: 30px; opacity: 1;} +} + +@-webkit-keyframes fadeout { + from {bottom: 30px; opacity: 1;} + to {bottom: 0; opacity: 0;} +} diff --git a/debug-db/src/main/assets/dataTables.altEditor.free.js b/debug-db-base/src/main/assets/dataTables.altEditor.free.js similarity index 99% rename from debug-db/src/main/assets/dataTables.altEditor.free.js rename to debug-db-base/src/main/assets/dataTables.altEditor.free.js index cf1773c..5e13831 100644 --- a/debug-db/src/main/assets/dataTables.altEditor.free.js +++ b/debug-db-base/src/main/assets/dataTables.altEditor.free.js @@ -735,4 +735,4 @@ DataTable.altEditor = altEditor; return altEditor; -})); \ No newline at end of file +})); diff --git a/debug-db/src/main/assets/dataTables.buttons.min.js b/debug-db-base/src/main/assets/dataTables.buttons.min.js similarity index 100% rename from debug-db/src/main/assets/dataTables.buttons.min.js rename to debug-db-base/src/main/assets/dataTables.buttons.min.js diff --git a/debug-db/src/main/assets/dataTables.responsive.min.js b/debug-db-base/src/main/assets/dataTables.responsive.min.js similarity index 100% rename from debug-db/src/main/assets/dataTables.responsive.min.js rename to debug-db-base/src/main/assets/dataTables.responsive.min.js diff --git a/debug-db/src/main/assets/dataTables.select.min.js b/debug-db-base/src/main/assets/dataTables.select.min.js similarity index 100% rename from debug-db/src/main/assets/dataTables.select.min.js rename to debug-db-base/src/main/assets/dataTables.select.min.js diff --git a/debug-db/src/main/assets/favicon.ico b/debug-db-base/src/main/assets/favicon.ico similarity index 100% rename from debug-db/src/main/assets/favicon.ico rename to debug-db-base/src/main/assets/favicon.ico diff --git a/debug-db-base/src/main/assets/fonts/glyphicons-halflings-regular.ttf b/debug-db-base/src/main/assets/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/debug-db-base/src/main/assets/fonts/glyphicons-halflings-regular.ttf differ diff --git a/debug-db-base/src/main/assets/index.html b/debug-db-base/src/main/assets/index.html new file mode 100644 index 0000000..4d3e7e4 --- /dev/null +++ b/debug-db-base/src/main/assets/index.html @@ -0,0 +1,321 @@ + + + + + + Android Debug Database + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    +
    +
    + + +
    + + + + + + + Customize UI + +
    +
    + + +
    + +
    +
    +
    Databases
    +
    +
    +
    +
    + +
    +
    +
    Tables
    +
    +
    +
    +
    + +
    +
    +
    Data
    +
    +
    +
    + +
    + + + +
    Data Updated Successfully
    + +
    + + + + + diff --git a/debug-db/src/main/assets/jquery.dataTables.min.css b/debug-db-base/src/main/assets/jquery.dataTables.min.css similarity index 100% rename from debug-db/src/main/assets/jquery.dataTables.min.css rename to debug-db-base/src/main/assets/jquery.dataTables.min.css diff --git a/debug-db/src/main/assets/jquery.dataTables.min.js b/debug-db-base/src/main/assets/jquery.dataTables.min.js similarity index 100% rename from debug-db/src/main/assets/jquery.dataTables.min.js rename to debug-db-base/src/main/assets/jquery.dataTables.min.js diff --git a/debug-db/src/main/assets/jquery.min.js b/debug-db-base/src/main/assets/jquery.min.js similarity index 100% rename from debug-db/src/main/assets/jquery.min.js rename to debug-db-base/src/main/assets/jquery.min.js diff --git a/debug-db/src/main/assets/responsive.dataTables.min.css b/debug-db-base/src/main/assets/responsive.dataTables.min.css similarity index 100% rename from debug-db/src/main/assets/responsive.dataTables.min.css rename to debug-db-base/src/main/assets/responsive.dataTables.min.css diff --git a/debug-db/src/main/assets/select.dataTables.min.css b/debug-db-base/src/main/assets/select.dataTables.min.css similarity index 100% rename from debug-db/src/main/assets/select.dataTables.min.css rename to debug-db-base/src/main/assets/select.dataTables.min.css diff --git a/debug-db-base/src/main/assets/ui-customization.js b/debug-db-base/src/main/assets/ui-customization.js new file mode 100644 index 0000000..808e5da --- /dev/null +++ b/debug-db-base/src/main/assets/ui-customization.js @@ -0,0 +1,731 @@ +/** + * UI customization for Android Debug Database. + * + * - Stored in browser localStorage (per-browser). + * - Visual-only settings. + * - Safe defaults that replicate existing UI. + */ +(function (global) { + "use strict"; + + var STORAGE_KEY = "addb.uiCustomization.v1"; + + var DEFAULT_SETTINGS = { + tableWidth: "fixed", // fixed | full | fluid + sidebarWidth: "standard", // standard | compact + rowDensity: "normal", // compact | normal | comfortable + wrapLongText: false, + maxColumnWidthPx: null, // number | null + jsonMode: "raw", // raw | wrapped | pretty + jsonMaxLines: 10, // number (limits cell height when JSON is shown) + stickyHeader: false, + pageLength: 10 // 10 | 25 | 50 | 100 + }; + + var allowedTableWidths = { fixed: true, full: true, fluid: true }; + var allowedSidebarWidths = { standard: true, compact: true }; + var allowedRowDensities = { compact: true, normal: true, comfortable: true }; + var allowedJsonModes = { raw: true, wrapped: true, pretty: true }; + var allowedPageLengths = { 10: true, 25: true, 50: true, 100: true }; + + var currentSettings = null; + var currentDataTable = null; + var currentDataTableDrawHandler = null; + var currentDataTableContainer = null; + var pendingDataTableRender = false; + var pendingColumnsAdjust = false; + + var JSON_INDENT_PX = 16; + + function safeParseJson(json) { + try { + return JSON.parse(json); + } catch (e) { + return null; + } + } + + function safeGetLocalStorageItem(key) { + try { + if (!global.localStorage) return null; + return global.localStorage.getItem(key); + } catch (e) { + return null; + } + } + + function safeSetLocalStorageItem(key, value) { + try { + if (!global.localStorage) return false; + global.localStorage.setItem(key, value); + return true; + } catch (e) { + return false; + } + } + + function safeRemoveLocalStorageItem(key) { + try { + if (!global.localStorage) return false; + global.localStorage.removeItem(key); + return true; + } catch (e) { + return false; + } + } + + function normalizeBoolean(value, fallback) { + if (value === true || value === false) return value; + if (value === "true") return true; + if (value === "false") return false; + return fallback; + } + + function clampInt(value, min, max) { + if (typeof value !== "number") return null; + if (value < min) return min; + if (value > max) return max; + return value; + } + + function normalizeSettings(raw) { + var settings = $.extend({}, DEFAULT_SETTINGS); + if (!raw || typeof raw !== "object") return settings; + + if (allowedTableWidths[raw.tableWidth]) settings.tableWidth = raw.tableWidth; + if (allowedSidebarWidths[raw.sidebarWidth]) settings.sidebarWidth = raw.sidebarWidth; + if (allowedRowDensities[raw.rowDensity]) settings.rowDensity = raw.rowDensity; + if (allowedJsonModes[raw.jsonMode]) settings.jsonMode = raw.jsonMode; + + settings.wrapLongText = normalizeBoolean(raw.wrapLongText, DEFAULT_SETTINGS.wrapLongText); + settings.stickyHeader = normalizeBoolean(raw.stickyHeader, DEFAULT_SETTINGS.stickyHeader); + + var pageLength = parseInt(raw.pageLength, 10); + if (allowedPageLengths[pageLength]) settings.pageLength = pageLength; + + var maxColumnWidthPx = raw.maxColumnWidthPx; + if (maxColumnWidthPx === "" || maxColumnWidthPx === null || typeof maxColumnWidthPx === "undefined") { + settings.maxColumnWidthPx = null; + } else { + var parsedMaxWidth = parseInt(maxColumnWidthPx, 10); + if (!isNaN(parsedMaxWidth)) { + settings.maxColumnWidthPx = clampInt(parsedMaxWidth, 120, 2000); + } + } + + var jsonMaxLinesRaw = raw.jsonMaxLines; + if (jsonMaxLinesRaw === "" || jsonMaxLinesRaw === null || typeof jsonMaxLinesRaw === "undefined") { + settings.jsonMaxLines = DEFAULT_SETTINGS.jsonMaxLines; + } else { + var parsedJsonMaxLines = parseInt(jsonMaxLinesRaw, 10); + if (!isNaN(parsedJsonMaxLines)) { + settings.jsonMaxLines = clampInt(parsedJsonMaxLines, 1, 200); + } + } + + return settings; + } + + function loadSettings() { + var raw = safeGetLocalStorageItem(STORAGE_KEY); + if (!raw) return normalizeSettings(null); + return normalizeSettings(safeParseJson(raw)); + } + + function saveSettings(settings) { + return safeSetLocalStorageItem(STORAGE_KEY, JSON.stringify(settings)); + } + + function clearSettings() { + return safeRemoveLocalStorageItem(STORAGE_KEY); + } + + function applyContainerWidth(settings) { + var container = $("#app-container"); + if (!container.length) return; + + container.removeClass("container container-fluid addb-container-fluid-max"); + + if (settings.tableWidth === "fixed") { + container.addClass("container"); + } else { + container.addClass("container-fluid"); + if (settings.tableWidth === "fluid") { + container.addClass("addb-container-fluid-max"); + } + } + } + + function setBootstrapSmColumn(el, span) { + if (!el || !el.length) return; + if (typeof span !== "number") return; + if (span < 1) span = 1; + if (span > 12) span = 12; + + var className = el.attr("class") || ""; + var classes = className.split(/\s+/).filter(Boolean); + var updated = []; + for (var i = 0; i < classes.length; i++) { + if (/^col-sm-\d+$/.test(classes[i])) continue; + updated.push(classes[i]); + } + updated.push("col-sm-" + span); + el.attr("class", updated.join(" ")); + } + + function applySidebarWidth(settings) { + var dbCol = $("#addb-db-column"); + var tableCol = $("#addb-table-column"); + var dataCol = $("#parent-data-div"); + if (!dbCol.length || !tableCol.length || !dataCol.length) return; + + var isCompact = settings.sidebarWidth === "compact"; + setBootstrapSmColumn(dbCol, isCompact ? 1 : 2); + setBootstrapSmColumn(tableCol, isCompact ? 1 : 2); + setBootstrapSmColumn(dataCol, isCompact ? 10 : 8); + } + + function applyBodyClasses(settings) { + var body = $("body"); + + body.removeClass("addb-density-compact addb-density-normal addb-density-comfortable"); + body.addClass("addb-density-" + settings.rowDensity); + + body.toggleClass("addb-wrap-long-text", !!settings.wrapLongText); + + body.removeClass("addb-json-mode-raw addb-json-mode-wrapped addb-json-mode-pretty"); + body.addClass("addb-json-mode-" + settings.jsonMode); + + body.toggleClass("addb-sticky-header", !!settings.stickyHeader); + + body.toggleClass("addb-sidebar-compact", settings.sidebarWidth === "compact"); + } + + function applyMaxColumnWidth(settings) { + var shouldApplyMaxWidth = + typeof settings.maxColumnWidthPx === "number" && + settings.maxColumnWidthPx > 0 && + (settings.wrapLongText || settings.jsonMode !== "raw"); + + var body = $("body"); + if (shouldApplyMaxWidth) { + document.documentElement.style.setProperty("--addb-max-col-width", settings.maxColumnWidthPx + "px"); + body.addClass("addb-max-col-width"); + } else { + document.documentElement.style.removeProperty("--addb-max-col-width"); + body.removeClass("addb-max-col-width"); + } + } + + function applyJsonMaxLines(settings) { + var jsonMaxLines = + typeof settings.jsonMaxLines === "number" && settings.jsonMaxLines > 0 + ? settings.jsonMaxLines + : DEFAULT_SETTINGS.jsonMaxLines; + + document.documentElement.style.setProperty("--addb-json-max-lines", String(jsonMaxLines)); + } + + function looksLikeJson(value) { + if (typeof value !== "string") return false; + var trimmed = value.trim(); + if (trimmed.length < 2) return false; + var startsOk = trimmed[0] === "{" || trimmed[0] === "["; + var endsOk = trimmed[trimmed.length - 1] === "}" || trimmed[trimmed.length - 1] === "]"; + return startsOk && endsOk; + } + + function formatJsonPrimitive(value) { + if (value === null) return "null"; + var t = typeof value; + if (t === "string" || t === "number" || t === "boolean") return JSON.stringify(value); + try { + return JSON.stringify(value); + } catch (e) { + return String(value); + } + } + + function createJsonLine(tagName, depth, extraClassName) { + var el = document.createElement(tagName || "div"); + el.className = "addb-json-line" + (extraClassName ? " " + extraClassName : ""); + el.style.paddingLeft = depth * JSON_INDENT_PX + "px"; + + var gutter = document.createElement("span"); + gutter.className = "addb-json-gutter"; + el.appendChild(gutter); + + return { el: el, gutter: gutter }; + } + + function createJsonDetails(depth, openByDefault) { + var details = document.createElement("details"); + details.className = "addb-json-node"; + if (openByDefault) details.open = true; + + var summaryLine = createJsonLine("summary", depth, "addb-json-summary"); + + var closedSpan = document.createElement("span"); + closedSpan.className = "addb-json-summary-closed"; + + var openSpan = document.createElement("span"); + openSpan.className = "addb-json-summary-open"; + + summaryLine.el.appendChild(closedSpan); + summaryLine.el.appendChild(openSpan); + + var children = document.createElement("div"); + children.className = "addb-json-children"; + + details.appendChild(summaryLine.el); + details.appendChild(children); + + return { + details: details, + closedSpan: closedSpan, + openSpan: openSpan, + children: children + }; + } + + function jsonContainerInfo(value) { + if (Array.isArray(value)) { + return { kind: "array", open: "[", close: "]", count: value.length }; + } + var keys = Object.keys(value); + return { kind: "object", open: "{", close: "}", count: keys.length, keys: keys }; + } + + function appendJsonValue(parent, value, depth, isLast, keyPrefix) { + var comma = isLast ? "" : ","; + var prefix = keyPrefix || ""; + + var isContainer = value !== null && typeof value === "object"; + if (!isContainer) { + var primitiveLine = createJsonLine("div", depth); + primitiveLine.el.appendChild(document.createTextNode(prefix + formatJsonPrimitive(value) + comma)); + parent.appendChild(primitiveLine.el); + return; + } + + var info = jsonContainerInfo(value); + var isEmpty = info.count === 0; + if (isEmpty) { + var emptyLine = createJsonLine("div", depth); + emptyLine.el.appendChild(document.createTextNode(prefix + info.open + info.close + comma)); + parent.appendChild(emptyLine.el); + return; + } + + // Collapse all 2nd-level (depth=1) container values by default. + var openByDefault = depth !== 1; + var node = createJsonDetails(depth, openByDefault); + + var collapsedPreview = info.kind === "array" ? "[\u2026] (" + info.count + ")" : "{\u2026} (" + info.count + ")"; + node.closedSpan.textContent = prefix + collapsedPreview + comma; + node.openSpan.textContent = prefix + info.open; + + if (info.kind === "array") { + for (var index = 0; index < value.length; index++) { + appendJsonValue(node.children, value[index], depth + 1, index === value.length - 1, ""); + } + } else { + var keys = info.keys; + for (var keyIndex = 0; keyIndex < keys.length; keyIndex++) { + var key = keys[keyIndex]; + var childPrefix = JSON.stringify(key) + ": "; + appendJsonValue(node.children, value[key], depth + 1, keyIndex === keys.length - 1, childPrefix); + } + } + + var closingLine = createJsonLine("div", depth); + closingLine.el.appendChild(document.createTextNode(info.close + comma)); + node.children.appendChild(closingLine.el); + + parent.appendChild(node.details); + } + + function buildJsonTree(parsedJson) { + var root = document.createElement("div"); + root.className = "addb-json-tree"; + + var info = jsonContainerInfo(parsedJson); + + var openLine = createJsonLine("div", 0); + openLine.el.appendChild(document.createTextNode(info.open)); + root.appendChild(openLine.el); + + if (info.kind === "array") { + for (var index = 0; index < parsedJson.length; index++) { + appendJsonValue(root, parsedJson[index], 1, index === parsedJson.length - 1, ""); + } + } else { + var keys = info.keys; + for (var keyIndex = 0; keyIndex < keys.length; keyIndex++) { + var key = keys[keyIndex]; + var childPrefix = JSON.stringify(key) + ": "; + appendJsonValue(root, parsedJson[key], 1, keyIndex === keys.length - 1, childPrefix); + } + } + + var closeLine = createJsonLine("div", 0); + closeLine.el.appendChild(document.createTextNode(info.close)); + root.appendChild(closeLine.el); + + return root; + } + + function scheduleColumnsAdjust(dataTableApi) { + if (!dataTableApi) return; + if (pendingColumnsAdjust) return; + pendingColumnsAdjust = true; + + global.setTimeout(function () { + pendingColumnsAdjust = false; + try { + var tableNode = $(dataTableApi.table().node()); + if (!tableNode.is(":visible")) return; + dataTableApi.columns.adjust(); + } catch (e) { + // Best-effort only. + } + }, 0); + } + + function renderWrappedJsonCell(td, jsonText) { + if ( + td.childNodes.length === 1 && + td.firstChild && + td.firstChild.nodeType === 1 && + td.firstChild.classList && + td.firstChild.classList.contains("addb-json-text") && + td.firstChild.textContent === jsonText + ) { + return false; + } + + td.textContent = ""; + var wrapper = document.createElement("div"); + wrapper.className = "addb-json-text"; + wrapper.textContent = jsonText; + td.appendChild(wrapper); + return true; + } + + function applyJsonFormattingToCurrentPage(dataTableApi, settings) { + if (!dataTableApi) return; + + var mode = settings.jsonMode; + var didMutateDom = false; + + var rows = dataTableApi.rows({ page: "current" }).nodes(); + for (var rowIndex = 0; rowIndex < rows.length; rowIndex++) { + var cells = rows[rowIndex].cells; + for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) { + var td = cells[cellIndex]; + td.classList.remove("addb-json-cell"); + + var cellData = dataTableApi.cell(td).data(); + if (!looksLikeJson(cellData)) continue; + + if (mode === "raw") { + // Restore raw JSON text (undo tree view, if present). + if (td.textContent !== cellData || td.childNodes.length !== 1 || td.firstChild.nodeType !== 3) { + td.textContent = cellData; + didMutateDom = true; + } + continue; + } + + var parsed = safeParseJson(cellData); + if (parsed === null) continue; + + td.classList.add("addb-json-cell"); + + if (mode === "wrapped") { + // Keep raw JSON text, but apply JSON cell styling. + if (renderWrappedJsonCell(td, cellData)) didMutateDom = true; + continue; + } + + // Pretty mode: render a collapsible JSON tree with 2nd-level containers collapsed. + td.textContent = ""; + td.appendChild(buildJsonTree(parsed)); + didMutateDom = true; + } + } + + if (didMutateDom) { + scheduleColumnsAdjust(dataTableApi); + } + } + + function applyDataTableSettings(dataTableApi, settings, reason) { + if (!dataTableApi) return; + + try { + if (typeof dataTableApi.page === "function" && typeof dataTableApi.page.len === "function") { + var currentLen = dataTableApi.page.len(); + if (currentLen !== settings.pageLength) { + dataTableApi.page.len(settings.pageLength); + } + } + + if (reason === "jsonMode") { + dataTableApi.rows().invalidate(); + } + + var tableNode = $(dataTableApi.table().node()); + var isVisible = tableNode.is(":visible"); + if (!isVisible) { + pendingDataTableRender = true; + return; + } + + dataTableApi.columns.adjust(); + dataTableApi.draw(false); + } catch (e) { + // Best-effort only; never break the main UI. + } + } + + function applySettings(settings, reason) { + var previousSettings = currentSettings || DEFAULT_SETTINGS; + currentSettings = normalizeSettings(settings); + + applyContainerWidth(currentSettings); + applySidebarWidth(currentSettings); + applyBodyClasses(currentSettings); + applyMaxColumnWidth(currentSettings); + applyJsonMaxLines(currentSettings); + + var dataTableReason = reason; + if (previousSettings.jsonMode !== currentSettings.jsonMode) dataTableReason = "jsonMode"; + + applyDataTableSettings(currentDataTable, currentSettings, dataTableReason); + } + + function setCurrentDataTable(dataTableApi) { + var previousTableNode = currentDataTable ? $(currentDataTable.table().node()) : null; + if (previousTableNode && currentDataTableDrawHandler) { + previousTableNode.off("draw.dt", currentDataTableDrawHandler); + } + if (currentDataTableContainer) { + currentDataTableContainer.off("click.addb-json"); + currentDataTableContainer = null; + } + + currentDataTable = dataTableApi || null; + if (!currentDataTable) return; + + var tableNode = $(currentDataTable.table().node()); + currentDataTableDrawHandler = function () { + applyJsonFormattingToCurrentPage(currentDataTable, currentSettings || DEFAULT_SETTINGS); + }; + tableNode.on("draw.dt", currentDataTableDrawHandler); + + currentDataTableContainer = $(currentDataTable.table().container()); + currentDataTableContainer.on("click.addb-json", "details.addb-json-node > summary", function () { + scheduleColumnsAdjust(currentDataTable); + }); + + applyDataTableSettings(currentDataTable, currentSettings || DEFAULT_SETTINGS, "init"); + } + + function getQueryValue(key) { + var query = global.location.search; + if (!query) return null; + var keyEscaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + var matches = query.match(new RegExp("[?&]" + keyEscaped + "=([^&]*)")); + return matches ? decodeURIComponent(matches[1].replace(/\+/g, " ")) : null; + } + + function setQueryValue(key, value) { + var currentSearch = global.location.search || ""; + var hash = global.location.hash || ""; + var base = global.location.pathname; + + var query = currentSearch.replace(/^\?/, ""); + var parts = query ? query.split("&") : []; + var updated = []; + var found = false; + + for (var i = 0; i < parts.length; i++) { + if (!parts[i]) continue; + var kv = parts[i].split("="); + var k = decodeURIComponent(kv[0] || ""); + if (k !== key) { + updated.push(parts[i]); + continue; + } + found = true; + if (value !== null && value !== "") { + updated.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); + } + } + + if (!found && value !== null && value !== "") { + updated.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); + } + + var newSearch = updated.length ? "?" + updated.join("&") : ""; + return base + newSearch + hash; + } + + function showView(viewName) { + var mainView = $("#addb-main-view"); + var customizeView = $("#addb-customize-view"); + + if (viewName === "customize") { + mainView.addClass("display-none"); + customizeView.removeClass("display-none"); + global.scrollTo(0, 0); + } else { + customizeView.addClass("display-none"); + mainView.removeClass("display-none"); + if (pendingDataTableRender && currentDataTable) { + pendingDataTableRender = false; + applyDataTableSettings(currentDataTable, currentSettings || DEFAULT_SETTINGS, "resume"); + } + global.scrollTo(0, 0); + } + } + + function syncViewFromUrl() { + var view = getQueryValue("view"); + showView(view === "customize" ? "customize" : "main"); + } + + function navigateTo(viewName) { + if (!global.history || typeof global.history.pushState !== "function") { + if (viewName === "customize") global.location.href = setQueryValue("view", "customize"); + else global.location.href = setQueryValue("view", null); + return; + } + + var url = viewName === "customize" ? setQueryValue("view", "customize") : setQueryValue("view", null); + global.history.pushState({ view: viewName }, "", url); + showView(viewName); + } + + function setActiveCategory(category) { + var categories = $("#addb-customize-categories .list-group-item"); + categories.removeClass("active"); + categories.filter("[data-category='" + category + "']").addClass("active"); + + $(".addb-customize-category").each(function () { + var el = $(this); + el.toggleClass("display-none", el.attr("data-category") !== category); + }); + } + + function readDraftFromForm() { + var maxWidthRaw = $("#addb-max-column-width").val(); + var jsonMaxLinesRaw = $("#addb-json-max-lines").val(); + var settings = { + tableWidth: $("input[name='addb-table-width']:checked").val(), + sidebarWidth: $("input[name='addb-sidebar-width']:checked").val(), + rowDensity: $("input[name='addb-row-density']:checked").val(), + wrapLongText: $("#addb-wrap-long-text").is(":checked"), + maxColumnWidthPx: maxWidthRaw === "" ? null : maxWidthRaw, + jsonMode: $("#addb-json-mode").val(), + jsonMaxLines: jsonMaxLinesRaw, + stickyHeader: $("#addb-sticky-header").is(":checked"), + pageLength: $("#addb-page-length").val() + }; + return normalizeSettings(settings); + } + + function writeFormFromSettings(settings) { + $("input[name='addb-table-width'][value='" + settings.tableWidth + "']").prop("checked", true); + $("input[name='addb-sidebar-width'][value='" + settings.sidebarWidth + "']").prop("checked", true); + $("input[name='addb-row-density'][value='" + settings.rowDensity + "']").prop("checked", true); + $("#addb-wrap-long-text").prop("checked", !!settings.wrapLongText); + $("#addb-max-column-width").val(settings.maxColumnWidthPx === null ? "" : settings.maxColumnWidthPx); + $("#addb-json-mode").val(settings.jsonMode); + $("#addb-json-max-lines").val(String(settings.jsonMaxLines)); + $("#addb-sticky-header").prop("checked", !!settings.stickyHeader); + $("#addb-page-length").val(String(settings.pageLength)); + } + + function initCustomizationUi() { + $("#open-ui-customization").on("click", function (e) { + e.preventDefault(); + navigateTo("customize"); + }); + + $("#addb-customize-back").on("click", function (e) { + e.preventDefault(); + navigateTo("main"); + }); + + $("#addb-customize-categories").on("click", ".list-group-item", function (e) { + e.preventDefault(); + setActiveCategory($(this).attr("data-category")); + }); + + $("#addb-customize-form").on("change input", "input, select", function () { + var draft = readDraftFromForm(); + applySettings(draft, "preview"); + }); + + $("#addb-ui-apply").on("click", function () { + var draft = readDraftFromForm(); + applySettings(draft, "apply"); + + var saved = saveSettings(currentSettings); + if (saved && typeof global.showSuccessInfo === "function") { + global.showSuccessInfo("UI settings saved"); + } else if (!saved && typeof global.showErrorInfo === "function") { + global.showErrorInfo("Could not save UI settings"); + } + }); + + $("#addb-ui-reset").on("click", function () { + clearSettings(); + applySettings(DEFAULT_SETTINGS, "reset"); + writeFormFromSettings(DEFAULT_SETTINGS); + if (typeof global.showSuccessInfo === "function") { + global.showSuccessInfo("UI settings reset to defaults"); + } + }); + + // Initial form + view state + writeFormFromSettings(currentSettings || DEFAULT_SETTINGS); + setActiveCategory("layout"); + syncViewFromUrl(); + + $(global).on("popstate", function () { + syncViewFromUrl(); + }); + } + + function init() { + currentSettings = loadSettings(); + applySettings(currentSettings, "init"); + initCustomizationUi(); + } + + global.AddbUiCustomization = { + defaults: DEFAULT_SETTINGS, + getSettings: function () { + return $.extend({}, currentSettings || DEFAULT_SETTINGS); + }, + apply: function (settings) { + applySettings(settings, "external"); + }, + save: function (settings) { + applySettings(settings, "external"); + return saveSettings(currentSettings); + }, + clear: function () { + clearSettings(); + applySettings(DEFAULT_SETTINGS, "external"); + }, + onDataTableCreated: function (dataTableApi) { + setCurrentDataTable(dataTableApi); + applyJsonFormattingToCurrentPage(currentDataTable, currentSettings || DEFAULT_SETTINGS); + }, + init: init + }; +})(window); diff --git a/debug-db/src/main/java/com/amitshekhar/DebugDB.java b/debug-db-base/src/main/java/com/amitshekhar/DebugDB.java similarity index 77% rename from debug-db/src/main/java/com/amitshekhar/DebugDB.java rename to debug-db-base/src/main/java/com/amitshekhar/DebugDB.java index 52e272e..ebcbbbf 100644 --- a/debug-db/src/main/java/com/amitshekhar/DebugDB.java +++ b/debug-db-base/src/main/java/com/amitshekhar/DebugDB.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,13 +21,16 @@ import android.content.Context; import android.util.Log; +import android.util.Pair; + +import androidx.sqlite.db.SupportSQLiteDatabase; import com.amitshekhar.server.ClientServer; +import com.amitshekhar.sqlite.DBFactory; import com.amitshekhar.utils.NetworkUtils; import java.io.File; import java.util.HashMap; -import java.util.Map; /** * Created by amitshekhar on 15/11/16. @@ -44,7 +47,7 @@ private DebugDB() { // This class in not publicly instantiable } - public static void initialize(Context context) { + public static void initialize(Context context, DBFactory dbFactory) { int portNumber; try { @@ -55,7 +58,7 @@ public static void initialize(Context context) { Log.i(TAG, "Using Default port : " + DEFAULT_PORT); } - clientServer = new ClientServer(context, portNumber); + clientServer = new ClientServer(context, portNumber, dbFactory); clientServer.start(); addressLog = NetworkUtils.getAddressLog(context, portNumber); Log.d(TAG, addressLog); @@ -73,12 +76,18 @@ public static void shutDown() { } } - public static void setCustomDatabaseFiles(HashMap customDatabaseFiles){ - if(clientServer!=null){ + public static void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { + if (clientServer != null) { clientServer.setCustomDatabaseFiles(customDatabaseFiles); } } - + + public static void setInMemoryRoomDatabases(HashMap databases) { + if (clientServer != null) { + clientServer.setInMemoryRoomDatabases(databases); + } + } + public static boolean isServerRunning() { return clientServer != null && clientServer.isRunning(); } diff --git a/debug-db/src/main/java/com/amitshekhar/model/Response.java b/debug-db-base/src/main/java/com/amitshekhar/model/Response.java similarity index 96% rename from debug-db/src/main/java/com/amitshekhar/model/Response.java rename to debug-db-base/src/main/java/com/amitshekhar/model/Response.java index 0b76ef9..9964cf9 100644 --- a/debug-db/src/main/java/com/amitshekhar/model/Response.java +++ b/debug-db-base/src/main/java/com/amitshekhar/model/Response.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/model/RowDataRequest.java b/debug-db-base/src/main/java/com/amitshekhar/model/RowDataRequest.java similarity index 95% rename from debug-db/src/main/java/com/amitshekhar/model/RowDataRequest.java rename to debug-db-base/src/main/java/com/amitshekhar/model/RowDataRequest.java index 7040542..c81f3c9 100644 --- a/debug-db/src/main/java/com/amitshekhar/model/RowDataRequest.java +++ b/debug-db-base/src/main/java/com/amitshekhar/model/RowDataRequest.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/model/TableDataResponse.java b/debug-db-base/src/main/java/com/amitshekhar/model/TableDataResponse.java similarity index 96% rename from debug-db/src/main/java/com/amitshekhar/model/TableDataResponse.java rename to debug-db-base/src/main/java/com/amitshekhar/model/TableDataResponse.java index 241bded..2c9a063 100644 --- a/debug-db/src/main/java/com/amitshekhar/model/TableDataResponse.java +++ b/debug-db-base/src/main/java/com/amitshekhar/model/TableDataResponse.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,7 @@ public static class TableInfo { public String title; public boolean isPrimary; } + public static class ColumnData { public String dataType; public Object value; diff --git a/debug-db/src/main/java/com/amitshekhar/model/UpdateRowResponse.java b/debug-db-base/src/main/java/com/amitshekhar/model/UpdateRowResponse.java similarity index 95% rename from debug-db/src/main/java/com/amitshekhar/model/UpdateRowResponse.java rename to debug-db-base/src/main/java/com/amitshekhar/model/UpdateRowResponse.java index 41adcca..592ebc5 100644 --- a/debug-db/src/main/java/com/amitshekhar/model/UpdateRowResponse.java +++ b/debug-db-base/src/main/java/com/amitshekhar/model/UpdateRowResponse.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/server/ClientServer.java b/debug-db-base/src/main/java/com/amitshekhar/server/ClientServer.java similarity index 77% rename from debug-db/src/main/java/com/amitshekhar/server/ClientServer.java rename to debug-db-base/src/main/java/com/amitshekhar/server/ClientServer.java index e187ad3..665b17b 100644 --- a/debug-db/src/main/java/com/amitshekhar/server/ClientServer.java +++ b/debug-db-base/src/main/java/com/amitshekhar/server/ClientServer.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,13 @@ * Created by amitshekhar on 15/11/16. */ - import android.content.Context; import android.util.Log; +import android.util.Pair; + +import androidx.sqlite.db.SupportSQLiteDatabase; + +import com.amitshekhar.sqlite.DBFactory; import java.io.File; import java.io.IOException; @@ -33,22 +37,18 @@ import java.net.Socket; import java.net.SocketException; import java.util.HashMap; -import java.util.Map; public class ClientServer implements Runnable { private static final String TAG = "ClientServer"; private final int mPort; - + private final RequestHandler mRequestHandler; private boolean mIsRunning; - private ServerSocket mServerSocket; - private final RequestHandler mRequestHandler; - - public ClientServer(Context context, int port) { - mRequestHandler = new RequestHandler(context); + public ClientServer(Context context, int port, DBFactory dbFactory) { + mRequestHandler = new RequestHandler(context, dbFactory); mPort = port; } @@ -83,12 +83,16 @@ public void run() { } catch (IOException e) { Log.e(TAG, "Web server error.", e); } catch (Exception ignore) { - + Log.e(TAG, "Exception.", ignore); } } - public void setCustomDatabaseFiles(HashMap customDatabaseFiles){ - mRequestHandler.setCustomDatabaseFiles(customDatabaseFiles); + public void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { + mRequestHandler.setCustomDatabaseFiles(customDatabaseFiles); + } + + public void setInMemoryRoomDatabases(HashMap databases) { + mRequestHandler.setInMemoryRoomDatabases(databases); } public boolean isRunning() { diff --git a/debug-db/src/main/java/com/amitshekhar/server/RequestHandler.java b/debug-db-base/src/main/java/com/amitshekhar/server/RequestHandler.java similarity index 64% rename from debug-db/src/main/java/com/amitshekhar/server/RequestHandler.java rename to debug-db-base/src/main/java/com/amitshekhar/server/RequestHandler.java index 58b6e2c..e7ba526 100644 --- a/debug-db/src/main/java/com/amitshekhar/server/RequestHandler.java +++ b/debug-db-base/src/main/java/com/amitshekhar/server/RequestHandler.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,14 +21,19 @@ import android.content.Context; import android.content.res.AssetManager; -import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.text.TextUtils; +import android.util.Pair; + +import androidx.sqlite.db.SupportSQLiteDatabase; import com.amitshekhar.model.Response; import com.amitshekhar.model.RowDataRequest; import com.amitshekhar.model.TableDataResponse; import com.amitshekhar.model.UpdateRowResponse; +import com.amitshekhar.sqlite.DBFactory; +import com.amitshekhar.sqlite.InMemoryDebugSQLiteDB; +import com.amitshekhar.sqlite.SQLiteDB; import com.amitshekhar.utils.Constants; import com.amitshekhar.utils.DatabaseFileProvider; import com.amitshekhar.utils.DatabaseHelper; @@ -57,16 +62,19 @@ public class RequestHandler { private final Context mContext; private final Gson mGson; private final AssetManager mAssets; + private final DBFactory mDbFactory; private boolean isDbOpened; - private SQLiteDatabase mDatabase; - private HashMap mDatabaseFiles; - private HashMap mCustomDatabaseFiles; + private SQLiteDB sqLiteDB; + private HashMap> mDatabaseFiles; + private HashMap> mCustomDatabaseFiles; private String mSelectedDatabase = null; + private HashMap mRoomInMemoryDatabases = new HashMap<>(); - public RequestHandler(Context context) { + public RequestHandler(Context context, DBFactory dbFactory) { mContext = context; mAssets = context.getResources().getAssets(); mGson = new GsonBuilder().serializeNulls().create(); + mDbFactory = dbFactory; } public void handle(Socket socket) throws IOException { @@ -94,33 +102,47 @@ public void handle(Socket socket) throws IOException { route = "index.html"; } + // Strip query params for static asset lookups and MIME type detection. + // (API routes still receive the full route, including query params.) + String routePath = route; + final int queryIndex = route.indexOf('?'); + if (queryIndex >= 0) { + routePath = route.substring(0, queryIndex); + if (routePath.isEmpty()) { + routePath = "index.html"; + } + } + byte[] bytes; - if (route.startsWith("getDbList")) { + if (routePath.startsWith("getDbList")) { final String response = getDBListResponse(); bytes = response.getBytes(); - } else if (route.startsWith("getAllDataFromTheTable")) { + } else if (routePath.startsWith("getAllDataFromTheTable")) { final String response = getAllDataFromTheTableResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("getTableList")) { + } else if (routePath.startsWith("getTableList")) { final String response = getTableListResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("addTableData")) { + } else if (routePath.startsWith("addTableData")) { final String response = addTableDataAndGetResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("updateTableData")) { + } else if (routePath.startsWith("updateTableData")) { final String response = updateTableDataAndGetResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("deleteTableData")) { + } else if (routePath.startsWith("deleteTableData")) { final String response = deleteTableDataAndGetResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("query")) { + } else if (routePath.startsWith("query")) { final String response = executeQueryAndGetResponse(route); bytes = response.getBytes(); - } else if (route.startsWith("downloadDb")) { + } else if (routePath.startsWith("deleteDb")) { + final String response = deleteSelectedDatabaseAndGetResponse(); + bytes = response.getBytes(); + } else if (routePath.startsWith("downloadDb")) { bytes = Utils.getDatabase(mSelectedDatabase, mDatabaseFiles); } else { - bytes = Utils.loadContent(route, mAssets); + bytes = Utils.loadContent(routePath, mAssets); } if (null == bytes) { @@ -130,9 +152,9 @@ public void handle(Socket socket) throws IOException { // Send out the content. output.println("HTTP/1.0 200 OK"); - output.println("Content-Type: " + Utils.detectMimeType(route)); + output.println("Content-Type: " + Utils.detectMimeType(routePath)); - if (route.startsWith("downloadDb")) { + if (routePath.startsWith("downloadDb")) { output.println("Content-Disposition: attachment; filename=" + mSelectedDatabase); } else { output.println("Content-Length: " + bytes.length); @@ -154,10 +176,14 @@ public void handle(Socket socket) throws IOException { } } - public void setCustomDatabaseFiles(HashMap customDatabaseFiles){ + public void setCustomDatabaseFiles(HashMap> customDatabaseFiles) { mCustomDatabaseFiles = customDatabaseFiles; } + public void setInMemoryRoomDatabases(HashMap databases) { + mRoomInMemoryDatabases = databases; + } + private void writeServerError(PrintStream output) { output.println("HTTP/1.0 500 Internal Server Error"); output.flush(); @@ -165,31 +191,43 @@ private void writeServerError(PrintStream output) { private void openDatabase(String database) { closeDatabase(); - File databaseFile = mDatabaseFiles.get(database); - mDatabase = SQLiteDatabase.openOrCreateDatabase(databaseFile.getAbsolutePath(), null); + if (mRoomInMemoryDatabases.containsKey(database)) { + sqLiteDB = new InMemoryDebugSQLiteDB(mRoomInMemoryDatabases.get(database)); + } else { + File databaseFile = mDatabaseFiles.get(database).first; + String password = mDatabaseFiles.get(database).second; + sqLiteDB = mDbFactory.create(mContext, databaseFile.getAbsolutePath(), password); + } isDbOpened = true; } private void closeDatabase() { - if (mDatabase != null && mDatabase.isOpen()) { - mDatabase.close(); + if (sqLiteDB != null && sqLiteDB.isOpen()) { + sqLiteDB.close(); } - mDatabase = null; + sqLiteDB = null; isDbOpened = false; } private String getDBListResponse() { mDatabaseFiles = DatabaseFileProvider.getDatabaseFiles(mContext); - if(mCustomDatabaseFiles!=null){ + if (mCustomDatabaseFiles != null) { mDatabaseFiles.putAll(mCustomDatabaseFiles); } Response response = new Response(); if (mDatabaseFiles != null) { - for (HashMap.Entry entry : mDatabaseFiles.entrySet()) { - response.rows.add(entry.getKey()); + for (HashMap.Entry> entry : mDatabaseFiles.entrySet()) { + String[] dbEntry = {entry.getKey(), !entry.getValue().second.equals("") ? "true" : "false", "true"}; + response.rows.add(dbEntry); + } + } + if (mRoomInMemoryDatabases != null) { + for (HashMap.Entry entry : mRoomInMemoryDatabases.entrySet()) { + String[] dbEntry = {entry.getKey(), "false", "false"}; + response.rows.add(dbEntry); } } - response.rows.add(Constants.APP_SHARED_PREFERENCES); + response.rows.add(new String[]{Constants.APP_SHARED_PREFERENCES, "false", "false"}); response.isSuccessful = true; return mGson.toJson(response); } @@ -206,7 +244,7 @@ private String getAllDataFromTheTableResponse(String route) { if (isDbOpened) { String sql = "SELECT * FROM " + tableName; - response = DatabaseHelper.getTableData(mDatabase, sql, tableName); + response = DatabaseHelper.getTableData(sqLiteDB, sql, tableName); } else { response = PrefHelper.getAllPrefData(mContext, tableName); } @@ -230,13 +268,25 @@ private String executeQueryAndGetResponse(String route) { } if (query != null) { - first = query.split(" ")[0].toLowerCase(); - if (first.equals("select") || first.equals("pragma")) { - TableDataResponse response = DatabaseHelper.getTableData(mDatabase, query, null); - data = mGson.toJson(response); - } else { - TableDataResponse response = DatabaseHelper.exec(mDatabase, query); - data = mGson.toJson(response); + String[] statements = query.split(";"); + + for (int i = 0; i < statements.length; i++) { + + String aQuery = statements[i].trim(); + first = aQuery.split(" ")[0].toLowerCase(); + if (first.equals("select") || first.equals("pragma")) { + TableDataResponse response = DatabaseHelper.getTableData(sqLiteDB, aQuery, null); + data = mGson.toJson(response); + if (!response.isSuccessful) { + break; + } + } else { + TableDataResponse response = DatabaseHelper.exec(sqLiteDB, aQuery); + data = mGson.toJson(response); + if (!response.isSuccessful) { + break; + } + } } } } catch (Exception e) { @@ -266,7 +316,7 @@ private String getTableListResponse(String route) { mSelectedDatabase = Constants.APP_SHARED_PREFERENCES; } else { openDatabase(database); - response = DatabaseHelper.getAllTableName(mDatabase); + response = DatabaseHelper.getAllTableName(sqLiteDB); mSelectedDatabase = database; } return mGson.toJson(response); @@ -284,7 +334,7 @@ private String addTableDataAndGetResponse(String route) { if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.addOrUpdateRow(mContext, tableName, rowDataRequests); } else { - response = DatabaseHelper.addRow(mDatabase, tableName, rowDataRequests); + response = DatabaseHelper.addRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { @@ -306,7 +356,7 @@ private String updateTableDataAndGetResponse(String route) { if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.addOrUpdateRow(mContext, tableName, rowDataRequests); } else { - response = DatabaseHelper.updateRow(mDatabase, tableName, rowDataRequests); + response = DatabaseHelper.updateRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { @@ -329,7 +379,7 @@ private String deleteTableDataAndGetResponse(String route) { if (Constants.APP_SHARED_PREFERENCES.equals(mSelectedDatabase)) { response = PrefHelper.deleteRow(mContext, tableName, rowDataRequests); } else { - response = DatabaseHelper.deleteRow(mDatabase, tableName, rowDataRequests); + response = DatabaseHelper.deleteRow(sqLiteDB, tableName, rowDataRequests); } return mGson.toJson(response); } catch (Exception e) { @@ -340,4 +390,30 @@ private String deleteTableDataAndGetResponse(String route) { } } + private String deleteSelectedDatabaseAndGetResponse() { + UpdateRowResponse response = new UpdateRowResponse(); + + if (mSelectedDatabase == null || !mDatabaseFiles.containsKey(mSelectedDatabase)) { + response.isSuccessful = false; + return mGson.toJson(response); + } + + try { + closeDatabase(); + + File dbFile = mDatabaseFiles.get(mSelectedDatabase).first; + response.isSuccessful = dbFile.delete(); + + if (response.isSuccessful) { + mDatabaseFiles.remove(mSelectedDatabase); + mCustomDatabaseFiles.remove(mSelectedDatabase); + } + + return mGson.toJson(response); + } catch (Exception e) { + e.printStackTrace(); + response.isSuccessful = false; + return mGson.toJson(response); + } + } } diff --git a/debug-db-base/src/main/java/com/amitshekhar/sqlite/DBFactory.java b/debug-db-base/src/main/java/com/amitshekhar/sqlite/DBFactory.java new file mode 100644 index 0000000..0378184 --- /dev/null +++ b/debug-db-base/src/main/java/com/amitshekhar/sqlite/DBFactory.java @@ -0,0 +1,9 @@ +package com.amitshekhar.sqlite; + +import android.content.Context; + +public interface DBFactory { + + SQLiteDB create(Context context, String path, String password); + +} diff --git a/debug-db-base/src/main/java/com/amitshekhar/sqlite/InMemoryDebugSQLiteDB.java b/debug-db-base/src/main/java/com/amitshekhar/sqlite/InMemoryDebugSQLiteDB.java new file mode 100644 index 0000000..1a804cc --- /dev/null +++ b/debug-db-base/src/main/java/com/amitshekhar/sqlite/InMemoryDebugSQLiteDB.java @@ -0,0 +1,60 @@ +package com.amitshekhar.sqlite; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; + +import androidx.sqlite.db.SupportSQLiteDatabase; + +/** + * Created by anandgaurav on 12/02/18. + */ + +public class InMemoryDebugSQLiteDB implements SQLiteDB { + + private final SupportSQLiteDatabase database; + + public InMemoryDebugSQLiteDB(SupportSQLiteDatabase database) { + this.database = database; + } + + @Override + public int delete(String table, String whereClause, String[] whereArgs) { + return database.delete(table, whereClause, whereArgs); + } + + @Override + public boolean isOpen() { + return database.isOpen(); + } + + @Override + public void close() { + // no ops + } + + @Override + public Cursor rawQuery(String sql, String[] selectionArgs) { + return database.query(sql, selectionArgs); + } + + @Override + public void execSQL(String sql) throws SQLException { + database.execSQL(sql); + } + + @Override + public long insert(String table, String nullColumnHack, ContentValues values) { + return database.insert(table, 0, values); + } + + @Override + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return database.update(table, 0, values, whereClause, whereArgs); + } + + @Override + public int getVersion() { + return database.getVersion(); + } +} diff --git a/debug-db-base/src/main/java/com/amitshekhar/sqlite/SQLiteDB.java b/debug-db-base/src/main/java/com/amitshekhar/sqlite/SQLiteDB.java new file mode 100644 index 0000000..df21e4e --- /dev/null +++ b/debug-db-base/src/main/java/com/amitshekhar/sqlite/SQLiteDB.java @@ -0,0 +1,30 @@ +package com.amitshekhar.sqlite; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; + + +/** + * Created by anandgaurav on 12/02/18. + */ + +public interface SQLiteDB { + + int delete(String table, String whereClause, String[] whereArgs); + + boolean isOpen(); + + void close(); + + Cursor rawQuery(String sql, String[] selectionArgs); + + void execSQL(String sql) throws SQLException; + + long insert(String table, String nullColumnHack, ContentValues values); + + int update(String table, ContentValues values, String whereClause, String[] whereArgs); + + int getVersion(); + +} diff --git a/debug-db/src/main/java/com/amitshekhar/utils/Constants.java b/debug-db-base/src/main/java/com/amitshekhar/utils/Constants.java similarity index 96% rename from debug-db/src/main/java/com/amitshekhar/utils/Constants.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/Constants.java index b4539cf..f5930a8 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/Constants.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/Constants.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/ConverterUtils.java b/debug-db-base/src/main/java/com/amitshekhar/utils/ConverterUtils.java similarity index 97% rename from debug-db/src/main/java/com/amitshekhar/utils/ConverterUtils.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/ConverterUtils.java index 1a0aab7..318090a 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/ConverterUtils.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/ConverterUtils.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/DataType.java b/debug-db-base/src/main/java/com/amitshekhar/utils/DataType.java similarity index 96% rename from debug-db/src/main/java/com/amitshekhar/utils/DataType.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/DataType.java index 123bb6a..eccaf7d 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/DataType.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/DataType.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java b/debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java new file mode 100644 index 0000000..473d44a --- /dev/null +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java @@ -0,0 +1,70 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.amitshekhar.utils; + +import android.content.Context; +import android.util.Pair; + +import java.io.File; +import java.text.MessageFormat; +import java.util.HashMap; + +/** + * Created by amitshekhar on 06/02/17. + */ + +public class DatabaseFileProvider { + + private final static String DB_PASSWORD_RESOURCE = "DB_PASSWORD_{0}"; + + private DatabaseFileProvider() { + // This class in not publicly instantiable + } + + public static HashMap> getDatabaseFiles(Context context) { + HashMap> databaseFiles = new HashMap<>(); + try { + for (String databaseName : context.databaseList()) { + String password = getDbPasswordFromStringResources(context, databaseName); + databaseFiles.put(databaseName, new Pair<>(context.getDatabasePath(databaseName), password)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return databaseFiles; + } + + private static String getDbPasswordFromStringResources(Context context, String name) { + String nameWithoutExt = name; + if (nameWithoutExt.endsWith(".db")) { + nameWithoutExt = nameWithoutExt.substring(0, nameWithoutExt.lastIndexOf('.')); + } + String resourceName = MessageFormat.format(DB_PASSWORD_RESOURCE, nameWithoutExt.toUpperCase()); + String password = ""; + + int resourceId = context.getResources().getIdentifier(resourceName, "string", context.getPackageName()); + + if (resourceId != 0) { + password = context.getString(resourceId); + } + + return password; + } +} diff --git a/debug-db/src/main/java/com/amitshekhar/utils/DatabaseHelper.java b/debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseHelper.java similarity index 90% rename from debug-db/src/main/java/com/amitshekhar/utils/DatabaseHelper.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseHelper.java index 739c0b8..adf36e6 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/DatabaseHelper.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/DatabaseHelper.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,14 +21,13 @@ import android.content.ContentValues; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; -import android.util.Log; import com.amitshekhar.model.Response; import com.amitshekhar.model.RowDataRequest; import com.amitshekhar.model.TableDataResponse; import com.amitshekhar.model.UpdateRowResponse; +import com.amitshekhar.sqlite.SQLiteDB; import java.util.ArrayList; import java.util.HashSet; @@ -44,9 +43,9 @@ private DatabaseHelper() { // This class in not publicly instantiable } - public static Response getAllTableName(SQLiteDatabase database) { + public static Response getAllTableName(SQLiteDB database) { Response response = new Response(); - Cursor c = database.rawQuery("SELECT name FROM sqlite_master WHERE type='table' OR type='view'", null); + Cursor c = database.rawQuery("SELECT name FROM sqlite_master WHERE type='table' OR type='view' ORDER BY name COLLATE NOCASE", null); if (c.moveToFirst()) { while (!c.isAfterLast()) { response.rows.add(c.getString(0)); @@ -63,7 +62,7 @@ public static Response getAllTableName(SQLiteDatabase database) { return response; } - public static TableDataResponse getTableData(SQLiteDatabase db, String selectQuery, String tableName) { + public static TableDataResponse getTableData(SQLiteDB db, String selectQuery, String tableName) { TableDataResponse tableData = new TableDataResponse(); tableData.isSelectQuery = true; @@ -125,6 +124,25 @@ public static TableDataResponse getTableData(SQLiteDatabase db, String selectQue tableData.isSuccessful = true; tableData.rows = new ArrayList<>(); + + String[] columnNames = cursor.getColumnNames(); + + List tableInfoListModified = new ArrayList<>(); + + for (String columnName : columnNames) { + for (TableDataResponse.TableInfo tableInfo : tableData.tableInfos) { + if (columnName.equals(tableInfo.title)) { + tableInfoListModified.add(tableInfo); + break; + } + } + } + + if (tableData.tableInfos.size() != tableInfoListModified.size()) { + tableData.tableInfos = tableInfoListModified; + tableData.isEditable = false; + } + if (cursor.getCount() > 0) { do { @@ -173,7 +191,7 @@ private static String getQuotedTableName(String tableName) { return String.format("[%s]", tableName); } - private static List getTableInfo(SQLiteDatabase db, String pragmaQuery) { + private static List getTableInfo(SQLiteDB db, String pragmaQuery) { Cursor cursor; try { @@ -219,7 +237,7 @@ private static List getTableInfo(SQLiteDatabase db, } - public static UpdateRowResponse addRow(SQLiteDatabase db, String tableName, + public static UpdateRowResponse addRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); @@ -261,7 +279,7 @@ public static UpdateRowResponse addRow(SQLiteDatabase db, String tableName, } - public static UpdateRowResponse updateRow(SQLiteDatabase db, String tableName, List rowDataRequests) { + public static UpdateRowResponse updateRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); @@ -316,7 +334,7 @@ public static UpdateRowResponse updateRow(SQLiteDatabase db, String tableName, L } - public static UpdateRowResponse deleteRow(SQLiteDatabase db, String tableName, + public static UpdateRowResponse deleteRow(SQLiteDB db, String tableName, List rowDataRequests) { UpdateRowResponse updateRowResponse = new UpdateRowResponse(); @@ -363,7 +381,7 @@ public static UpdateRowResponse deleteRow(SQLiteDatabase db, String tableName, } - public static TableDataResponse exec(SQLiteDatabase database, String sql) { + public static TableDataResponse exec(SQLiteDB database, String sql) { TableDataResponse tableDataResponse = new TableDataResponse(); tableDataResponse.isSelectQuery = false; try { @@ -387,7 +405,7 @@ public static TableDataResponse exec(SQLiteDatabase database, String sql) { } private static String getTableName(String selectQuery) { - // TODO: 24/4/17 Handle JOIN Query + // TODO: Handle JOIN Query TableNameParser tableNameParser = new TableNameParser(selectQuery); HashSet tableNames = (HashSet) tableNameParser.tables(); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/NetworkUtils.java b/debug-db-base/src/main/java/com/amitshekhar/utils/NetworkUtils.java similarity index 97% rename from debug-db/src/main/java/com/amitshekhar/utils/NetworkUtils.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/NetworkUtils.java index 1201a46..b18a087 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/NetworkUtils.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/NetworkUtils.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/PrefHelper.java b/debug-db-base/src/main/java/com/amitshekhar/utils/PrefHelper.java similarity index 99% rename from debug-db/src/main/java/com/amitshekhar/utils/PrefHelper.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/PrefHelper.java index ab8f01c..f7d61d9 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/PrefHelper.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/PrefHelper.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/TableNameParser.java b/debug-db-base/src/main/java/com/amitshekhar/utils/TableNameParser.java similarity index 99% rename from debug-db/src/main/java/com/amitshekhar/utils/TableNameParser.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/TableNameParser.java index f297bfe..073460e 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/TableNameParser.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/TableNameParser.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/debug-db/src/main/java/com/amitshekhar/utils/Utils.java b/debug-db-base/src/main/java/com/amitshekhar/utils/Utils.java similarity index 92% rename from debug-db/src/main/java/com/amitshekhar/utils/Utils.java rename to debug-db-base/src/main/java/com/amitshekhar/utils/Utils.java index 93ec957..da85430 100644 --- a/debug-db/src/main/java/com/amitshekhar/utils/Utils.java +++ b/debug-db-base/src/main/java/com/amitshekhar/utils/Utils.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ import android.content.res.AssetManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.io.ByteArrayOutputStream; import java.io.File; @@ -82,14 +83,14 @@ public static byte[] loadContent(String fileName, AssetManager assetManager) thr } } - public static byte[] getDatabase(String selectedDatabase, HashMap databaseFiles) { - if (TextUtils.isEmpty(selectedDatabase)) { + public static byte[] getDatabase(String selectedDatabase, HashMap> databaseFiles) { + if (TextUtils.isEmpty(selectedDatabase) || !databaseFiles.containsKey(selectedDatabase)) { return null; } byte[] byteArray = new byte[0]; try { - File file = databaseFiles.get(selectedDatabase); + File file = databaseFiles.get(selectedDatabase).first; byteArray = null; try { diff --git a/debug-db-base/src/main/res/values/strings.xml b/debug-db-base/src/main/res/values/strings.xml new file mode 100644 index 0000000..828fe22 --- /dev/null +++ b/debug-db-base/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + + Debug-DB + diff --git a/debug-db/src/test/java/com/amitshekhar/ExampleUnitTest.java b/debug-db-base/src/test/java/com/amitshekhar/ExampleUnitTest.java similarity index 96% rename from debug-db/src/test/java/com/amitshekhar/ExampleUnitTest.java rename to debug-db-base/src/test/java/com/amitshekhar/ExampleUnitTest.java index 703f13a..9758539 100644 --- a/debug-db/src/test/java/com/amitshekhar/ExampleUnitTest.java +++ b/debug-db-base/src/test/java/com/amitshekhar/ExampleUnitTest.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,4 +33,4 @@ public class ExampleUnitTest { public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } -} \ No newline at end of file +} diff --git a/app/.gitignore b/debug-db-encrypt/.gitignore similarity index 100% rename from app/.gitignore rename to debug-db-encrypt/.gitignore diff --git a/debug-db-encrypt/build.gradle b/debug-db-encrypt/build.gradle new file mode 100644 index 0000000..8b1687e --- /dev/null +++ b/debug-db-encrypt/build.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + compileSdk rootProject.ext.compileSdk + defaultConfig { + minSdk rootProject.ext.minSdk + targetSdk rootProject.ext.targetSdk + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + api project(':debug-db-base') + implementation 'net.zetetic:android-database-sqlcipher:3.5.9' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/debug-db-encrypt/gradle.properties b/debug-db-encrypt/gradle.properties new file mode 100644 index 0000000..66d28d1 --- /dev/null +++ b/debug-db-encrypt/gradle.properties @@ -0,0 +1 @@ +ARTIFACT_ID=debug-db-enncrypt \ No newline at end of file diff --git a/debug-db-encrypt/proguard-rules.pro b/debug-db-encrypt/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/debug-db-encrypt/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/debug-db-encrypt/src/androidTest/java/com/amitshekhar/debug/encrypt/ExampleInstrumentedTest.java b/debug-db-encrypt/src/androidTest/java/com/amitshekhar/debug/encrypt/ExampleInstrumentedTest.java new file mode 100644 index 0000000..c8d27e0 --- /dev/null +++ b/debug-db-encrypt/src/androidTest/java/com/amitshekhar/debug/encrypt/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.amitshekhar.debug.encrypt; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.amitshekhar.debug.encrypt.test", appContext.getPackageName()); + } +} diff --git a/debug-db-encrypt/src/main/AndroidManifest.xml b/debug-db-encrypt/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b67d18a --- /dev/null +++ b/debug-db-encrypt/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/DebugDBEncryptInitProvider.java b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/DebugDBEncryptInitProvider.java new file mode 100644 index 0000000..9403b4f --- /dev/null +++ b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/DebugDBEncryptInitProvider.java @@ -0,0 +1,63 @@ +package com.amitshekhar.debug.encrypt; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; + +import com.amitshekhar.DebugDB; +import com.amitshekhar.debug.encrypt.sqlite.DebugDBEncryptFactory; + +public class DebugDBEncryptInitProvider extends ContentProvider { + + + public DebugDBEncryptInitProvider() { + } + + @Override + public boolean onCreate() { + DebugDB.initialize(getContext(), new DebugDBEncryptFactory()); + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public void attachInfo(Context context, ProviderInfo providerInfo) { + if (providerInfo == null) { + throw new NullPointerException("DebugDBEncryptInitProvider ProviderInfo cannot be null."); + } + // So if the authorities equal the library internal ones, the developer forgot to set his applicationId + if ("com.amitshekhar.debug.encrypt.DebugDBEncryptInitProvider".equals(providerInfo.authority)) { + throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " + + "missing applicationId variable in application\'s build.gradle."); + } + super.attachInfo(context, providerInfo); + } + +} diff --git a/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugDBEncryptFactory.java b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugDBEncryptFactory.java new file mode 100644 index 0000000..af0294a --- /dev/null +++ b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugDBEncryptFactory.java @@ -0,0 +1,18 @@ +package com.amitshekhar.debug.encrypt.sqlite; + +import android.content.Context; + +import com.amitshekhar.sqlite.DBFactory; +import com.amitshekhar.sqlite.SQLiteDB; + +import net.sqlcipher.database.SQLiteDatabase; + +public class DebugDBEncryptFactory implements DBFactory { + + @Override + public SQLiteDB create(Context context, String path, String password) { + SQLiteDatabase.loadLibs(context); + return new DebugEncryptSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, password, null)); + } + +} diff --git a/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugEncryptSQLiteDB.java b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugEncryptSQLiteDB.java new file mode 100644 index 0000000..5c387cb --- /dev/null +++ b/debug-db-encrypt/src/main/java/com/amitshekhar/debug/encrypt/sqlite/DebugEncryptSQLiteDB.java @@ -0,0 +1,62 @@ +package com.amitshekhar.debug.encrypt.sqlite; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; + +import com.amitshekhar.sqlite.SQLiteDB; + +import net.sqlcipher.database.SQLiteDatabase; + +/** + * Created by anandgaurav on 12/02/18. + */ + +public class DebugEncryptSQLiteDB implements SQLiteDB { + + private final SQLiteDatabase database; + + public DebugEncryptSQLiteDB(SQLiteDatabase database) { + this.database = database; + } + + @Override + public int delete(String table, String whereClause, String[] whereArgs) { + return database.delete(table, whereClause, whereArgs); + } + + @Override + public boolean isOpen() { + return database.isOpen(); + } + + @Override + public void close() { + database.close(); + } + + @Override + public Cursor rawQuery(String sql, String[] selectionArgs) { + return database.rawQuery(sql, selectionArgs); + } + + @Override + public void execSQL(String sql) throws SQLException { + database.execSQL(sql); + } + + @Override + public long insert(String table, String nullColumnHack, ContentValues values) { + return database.insert(table, nullColumnHack, values); + } + + @Override + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return database.update(table, values, whereClause, whereArgs); + } + + @Override + public int getVersion() { + return database.getVersion(); + } +} diff --git a/debug-db-encrypt/src/main/res/values/strings.xml b/debug-db-encrypt/src/main/res/values/strings.xml new file mode 100644 index 0000000..a2ab4c8 --- /dev/null +++ b/debug-db-encrypt/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + debug-db-encrypt + diff --git a/debug-db-encrypt/src/test/java/com/amitshekhar/debug/encrypt/ExampleUnitTest.java b/debug-db-encrypt/src/test/java/com/amitshekhar/debug/encrypt/ExampleUnitTest.java new file mode 100644 index 0000000..f470b79 --- /dev/null +++ b/debug-db-encrypt/src/test/java/com/amitshekhar/debug/encrypt/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.amitshekhar.debug.encrypt; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/debug-db/build.gradle b/debug-db/build.gradle index bd46682..b64fe8a 100644 --- a/debug-db/build.gradle +++ b/debug-db/build.gradle @@ -1,54 +1,25 @@ -/* - * - * * Copyright (C) 2016 Amit Shekhar - * * Copyright (C) 2011 Android Open Source Project - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - apply plugin: 'com.android.library' android { - compileSdkVersion 25 - buildToolsVersion "24.0.3" - + compileSdk rootProject.ext.compileSdk defaultConfig { - minSdkVersion 9 - targetSdkVersion 25 + minSdk rootProject.ext.minSdk + targetSdk rootProject.ext.targetSdk versionCode 1 versionName "1.0" - - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - - resValue("string", "PORT_NUMBER", "8080") + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - testCompile 'junit:junit:4.12' - compile 'com.google.code.gson:gson:2.8.0' -} - -//apply from: 'debug-db-upload.gradle' \ No newline at end of file + api project(':debug-db-base') + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/debug-db/debug-db-upload.gradle b/debug-db/debug-db-upload.gradle deleted file mode 100755 index 24f61c8..0000000 --- a/debug-db/debug-db-upload.gradle +++ /dev/null @@ -1,103 +0,0 @@ -/* - * - * * Copyright (C) 2016 Amit Shekhar - * * Copyright (C) 2011 Android Open Source Project - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: "com.jfrog.bintray" - -def siteUrl = 'https://github.com/amitshekhariitbhu/Android-Debug-Database' -def gitUrl = 'https://github.com/amitshekhariitbhu/Android-Debug-Database.git' - -group = "com.amitshekhar.android" -version = '1.0.1' - -install { - repositories.mavenInstaller { - pom.project { - packaging 'aar' - - name 'Android Debug Database' - description 'Android Debug Database is a powerful library for debugging databases in Android applications' - - url siteUrl - - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - developers { - developer { - id 'amitshekhariitbhu' - name 'Amit Shekhar' - email 'amit.shekhar.iitbhu@gmail.com' - } - } - - scm { - connection gitUrl - developerConnection gitUrl - url siteUrl - } - } - } -} - -task sourcesJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' -} - -task javadoc(type: Javadoc) { - source = android.sourceSets.main.java.srcDirs - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - classpath += configurations.compile -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} -artifacts { - archives javadocJar - archives sourcesJar -} - -if (project.rootProject.file("local.properties").exists()) { - Properties properties = new Properties() - properties.load(project.rootProject.file('local.properties').newDataInputStream()) - - bintray { - user = properties.getProperty("bintray.user") - key = properties.getProperty("bintray.apikey") - - configurations = ['archives'] - dryRun = false - - pkg { - repo = "maven" - name = "debug-db" - websiteUrl = siteUrl - vcsUrl = gitUrl - licenses = ["Apache-2.0"] - publish = true - } - } -} diff --git a/debug-db/gradle.properties b/debug-db/gradle.properties new file mode 100644 index 0000000..f842e08 --- /dev/null +++ b/debug-db/gradle.properties @@ -0,0 +1 @@ +ARTIFACT_ID=debug-db \ No newline at end of file diff --git a/debug-db/proguard-rules.pro b/debug-db/proguard-rules.pro index 885cf2d..f1b4245 100644 --- a/debug-db/proguard-rules.pro +++ b/debug-db/proguard-rules.pro @@ -1,14 +1,10 @@ # Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /Users/amitshekhar/Library/Android/sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# Add any project specific keep options here: - # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: @@ -16,60 +12,10 @@ # public *; #} --keepparameternames --renamesourcefileattribute SourceFile --keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,EnclosingMethod - -# Preserve all annotations. - --keepattributes *Annotation* - -# Preserve all public classes, and their public and protected fields and -# methods. - --keep public class * { - public protected *; -} - -# Preserve all .class method names. - --keepclassmembernames class * { - java.lang.Class class$(java.lang.String); - java.lang.Class class$(java.lang.String, boolean); -} - -# Preserve all native method names and the names of their classes. - --keepclasseswithmembernames class * { - native ; -} - -# Preserve the special static methods that are required in all enumeration -# classes. - --keepclassmembers class * extends java.lang.Enum { - public static **[] values(); - public static ** valueOf(java.lang.String); -} - -# Explicitly preserve all serialization members. The Serializable interface -# is only a marker interface, so it wouldn't save them. -# You can comment this out if your library doesn't use serialization. -# If your code contains serializable classes that have to be backward -# compatible, please refer to the manual. - --keepclassmembers class * implements java.io.Serializable { - static final long serialVersionUID; - static final java.io.ObjectStreamField[] serialPersistentFields; - private void writeObject(java.io.ObjectOutputStream); - private void readObject(java.io.ObjectInputStream); - java.lang.Object writeReplace(); - java.lang.Object readResolve(); -} - -# Your library may contain more items that need to be preserved; -# typically classes that are dynamically created using Class.forName: +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable -# -keep public class mypackage.MyClass -# -keep public interface mypackage.MyInterface -# -keep public class * implements mypackage.MyInterface \ No newline at end of file +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/debug-db/src/androidTest/java/com/amitshekhar/debug/ExampleInstrumentedTest.java b/debug-db/src/androidTest/java/com/amitshekhar/debug/ExampleInstrumentedTest.java new file mode 100644 index 0000000..29288c2 --- /dev/null +++ b/debug-db/src/androidTest/java/com/amitshekhar/debug/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.amitshekhar.debug; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.amitshekhar.debug.test", appContext.getPackageName()); + } +} diff --git a/debug-db/src/main/AndroidManifest.xml b/debug-db/src/main/AndroidManifest.xml index bb8d603..37fffaa 100644 --- a/debug-db/src/main/AndroidManifest.xml +++ b/debug-db/src/main/AndroidManifest.xml @@ -1,34 +1,12 @@ - - - - - + package="com.amitshekhar.debug"> + android:exported="false" /> diff --git a/debug-db/src/main/assets/custom.css b/debug-db/src/main/assets/custom.css deleted file mode 100644 index e160066..0000000 --- a/debug-db/src/main/assets/custom.css +++ /dev/null @@ -1,58 +0,0 @@ -.padding-fifty { - padding-top: 50px; -} - -.padding-twenty { - padding-top: 20px; -} - -.display-none { - display: none; -} - -.list-group-item { - word-break: break-all; -} - -.list-group-item.selected { - background: #dff0d8 !important; - color: #3c763d !important; - font-weight: bold; -} - -#snackbar { - visibility: hidden; - min-width: 250px; - margin-left: -125px; - background-color: #5cb85c; - color: #fff; - text-align: center; - border-radius: 2px; - padding: 16px; - position: fixed; - z-index: 1; - left: 50%; - bottom: 30px; - font-size: 17px; -} - -#snackbar.show { - visibility: visible; - -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; - animation: fadein 0.5s, fadeout 0.5s 2.5s; -} - -@-webkit-keyframes fadein { - from {bottom: 0; opacity: 0;} - to {bottom: 30px; opacity: 1;} -} - -@keyframes fadein { - from {bottom: 0; opacity: 0;} - to {bottom: 30px; opacity: 1;} -} - -@-webkit-keyframes fadeout { - from {bottom: 30px; opacity: 1;} - to {bottom: 0; opacity: 0;} -} \ No newline at end of file diff --git a/debug-db/src/main/assets/index.html b/debug-db/src/main/assets/index.html deleted file mode 100644 index ba8dc96..0000000 --- a/debug-db/src/main/assets/index.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - Android Debug Database - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    -
    -
    - - -
    - - -
    -
    - - -
    - -
    -
    -
    Databases
    -
    -
    -
    -
    - -
    -
    -
    Tables
    -
    -
    -
    -
    - -
    -
    -
    Data
    -
    -
    -
    - -
    Data Updated Successfully
    - -
    - - - - \ No newline at end of file diff --git a/debug-db/src/main/java/com/amitshekhar/DebugDBInitProvider.java b/debug-db/src/main/java/com/amitshekhar/debug/DebugDBInitProvider.java similarity index 88% rename from debug-db/src/main/java/com/amitshekhar/DebugDBInitProvider.java rename to debug-db/src/main/java/com/amitshekhar/debug/DebugDBInitProvider.java index c99f634..d9d88c1 100644 --- a/debug-db/src/main/java/com/amitshekhar/DebugDBInitProvider.java +++ b/debug-db/src/main/java/com/amitshekhar/debug/DebugDBInitProvider.java @@ -1,6 +1,6 @@ /* * - * * Copyright (C) 2016 Amit Shekhar + * * Copyright (C) 2019 Amit Shekhar * * Copyright (C) 2011 Android Open Source Project * * * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ * */ -package com.amitshekhar; +package com.amitshekhar.debug; import android.content.ContentProvider; import android.content.ContentValues; @@ -26,6 +26,9 @@ import android.database.Cursor; import android.net.Uri; +import com.amitshekhar.DebugDB; +import com.amitshekhar.debug.sqlite.DebugDBFactory; + /** * Created by amitshekhar on 16/11/16. */ @@ -38,7 +41,7 @@ public DebugDBInitProvider() { @Override public boolean onCreate() { - DebugDB.initialize(getContext()); + DebugDB.initialize(getContext(), new DebugDBFactory()); return true; } @@ -73,7 +76,7 @@ public void attachInfo(Context context, ProviderInfo providerInfo) { throw new NullPointerException("DebugDBInitProvider ProviderInfo cannot be null."); } // So if the authorities equal the library internal ones, the developer forgot to set his applicationId - if ("com.amitshekhar.DebugDBInitProvider".equals(providerInfo.authority)) { + if ("com.amitshekhar.debug.DebugDBInitProvider".equals(providerInfo.authority)) { throw new IllegalStateException("Incorrect provider authority in manifest. Most likely due to a " + "missing applicationId variable in application\'s build.gradle."); } diff --git a/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugDBFactory.java b/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugDBFactory.java new file mode 100644 index 0000000..3a797f5 --- /dev/null +++ b/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugDBFactory.java @@ -0,0 +1,16 @@ +package com.amitshekhar.debug.sqlite; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.amitshekhar.sqlite.DBFactory; +import com.amitshekhar.sqlite.SQLiteDB; + +public class DebugDBFactory implements DBFactory { + + @Override + public SQLiteDB create(Context context, String path, String password) { + return new DebugSQLiteDB(SQLiteDatabase.openOrCreateDatabase(path, null)); + } + +} diff --git a/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugSQLiteDB.java b/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugSQLiteDB.java new file mode 100644 index 0000000..fa6512c --- /dev/null +++ b/debug-db/src/main/java/com/amitshekhar/debug/sqlite/DebugSQLiteDB.java @@ -0,0 +1,57 @@ +package com.amitshekhar.debug.sqlite; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +import com.amitshekhar.sqlite.SQLiteDB; + +public class DebugSQLiteDB implements SQLiteDB { + + private final SQLiteDatabase database; + + public DebugSQLiteDB(SQLiteDatabase database) { + this.database = database; + } + + @Override + public int delete(String table, String whereClause, String[] whereArgs) { + return database.delete(table, whereClause, whereArgs); + } + + @Override + public boolean isOpen() { + return database.isOpen(); + } + + @Override + public void close() { + database.close(); + } + + @Override + public Cursor rawQuery(String sql, String[] selectionArgs) { + return database.rawQuery(sql, selectionArgs); + } + + @Override + public void execSQL(String sql) throws SQLException { + database.execSQL(sql); + } + + @Override + public long insert(String table, String nullColumnHack, ContentValues values) { + return database.insert(table, nullColumnHack, values); + } + + @Override + public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { + return database.update(table, values, whereClause, whereArgs); + } + + @Override + public int getVersion() { + return database.getVersion(); + } +} diff --git a/debug-db/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java b/debug-db/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java deleted file mode 100644 index c8eb6cf..0000000 --- a/debug-db/src/main/java/com/amitshekhar/utils/DatabaseFileProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * * Copyright (C) 2016 Amit Shekhar - * * Copyright (C) 2011 Android Open Source Project - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); - * * you may not use this file except in compliance with the License. - * * You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software - * * distributed under the License is distributed on an "AS IS" BASIS, - * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * * See the License for the specific language governing permissions and - * * limitations under the License. - * - */ - -package com.amitshekhar.utils; - -import android.content.Context; - -import java.io.File; -import java.util.HashMap; -import java.util.List; - -/** - * Created by amitshekhar on 06/02/17. - */ - -public class DatabaseFileProvider { - - private DatabaseFileProvider() { - // This class in not publicly instantiable - } - - public static HashMap getDatabaseFiles(Context context) { - HashMap databaseFiles = new HashMap<>(); - try { - for (String databaseName : context.databaseList()) { - databaseFiles.put(databaseName, context.getDatabasePath(databaseName)); - } - } catch (Exception e) { - e.printStackTrace(); - } - return databaseFiles; - } - -} diff --git a/debug-db/src/main/res/values/strings.xml b/debug-db/src/main/res/values/strings.xml index 0e49c69..e9e9569 100644 --- a/debug-db/src/main/res/values/strings.xml +++ b/debug-db/src/main/res/values/strings.xml @@ -1,22 +1,3 @@ - - - Debug-DB + debug-db diff --git a/debug-db/src/test/java/com/amitshekhar/debug/ExampleUnitTest.java b/debug-db/src/test/java/com/amitshekhar/debug/ExampleUnitTest.java new file mode 100644 index 0000000..fdac728 --- /dev/null +++ b/debug-db/src/test/java/com/amitshekhar/debug/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.amitshekhar.debug; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 515142f..a4a131a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # # /* -# * Copyright (C) 2016 Amit Shekhar +# * Copyright (C) 2019 Amit Shekhar # * Copyright (C) 2011 Android Open Source Project # * # * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,3 +34,8 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +android.useAndroidX=true +android.enableJetifier=true +signing.keyId=xxxxxx +signing.password=xxxxxx +signing.secretKeyRingFile=xxxxxx.gpg \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 13372ae..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 859a010..b4fa61e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,25 +1,6 @@ -# -# /* -# * Copyright (C) 2016 Amit Shekhar -# * Copyright (C) 2011 Android Open Source Project -# * -# * Licensed under the Apache License, Version 2.0 (the "License"); -# * you may not use this file except in compliance with the License. -# * You may obtain a copy of the License at -# * -# * http://www.apache.org/licenses/LICENSE-2.0 -# * -# * Unless required by applicable law or agreed to in writing, software -# * distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. -# */ -# - -#Mon Dec 28 10:00:20 PST 2015 +#Thu Apr 07 10:15:47 CST 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 9d82f78..cccdd3d 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,20 +6,38 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,84 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sample-app-encrypt/.gitignore b/sample-app-encrypt/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample-app-encrypt/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample-app-encrypt/build.gradle b/sample-app-encrypt/build.gradle new file mode 100644 index 0000000..b048340 --- /dev/null +++ b/sample-app-encrypt/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.application' + +android { + compileSdk rootProject.ext.compileSdk + defaultConfig { + minSdk rootProject.ext.minSdk + applicationId "com.sample.encrypt" + targetSdk rootProject.ext.targetSdk + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + } + buildTypes { + debug { + resValue("string", "PORT_NUMBER", "8080") + resValue("string", "DB_PASSWORD_PERSON", "a_password") + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + debugImplementation project(':debug-db-encrypt') + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'net.zetetic:android-database-sqlcipher:3.5.9' + implementation "androidx.room:room-runtime:2.5.0" + annotationProcessor "androidx.room:room-compiler:2.5.0" + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} diff --git a/sample-app-encrypt/proguard-rules.pro b/sample-app-encrypt/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/sample-app-encrypt/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/sample-app-encrypt/src/androidTest/java/com/sample/encrypt/ExampleInstrumentedTest.java b/sample-app-encrypt/src/androidTest/java/com/sample/encrypt/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f9938b2 --- /dev/null +++ b/sample-app-encrypt/src/androidTest/java/com/sample/encrypt/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.sample.encrypt; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.sample.encrypt", appContext.getPackageName()); + } +} diff --git a/sample-app-encrypt/src/main/AndroidManifest.xml b/sample-app-encrypt/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6c53578 --- /dev/null +++ b/sample-app-encrypt/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/MainActivity.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/MainActivity.java new file mode 100644 index 0000000..ab5035d --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/MainActivity.java @@ -0,0 +1,148 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.sample.encrypt; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.View; + +import androidx.appcompat.app.AppCompatActivity; + +import com.sample.encrypt.database.CarDBHelper; +import com.sample.encrypt.database.ContactDBHelper; +import com.sample.encrypt.database.ExtTestDBHelper; +import com.sample.encrypt.database.PersonDBHelper; +import com.sample.encrypt.database.room.User; +import com.sample.encrypt.database.room.UserDBHelper; +import com.sample.encrypt.utils.Utils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class MainActivity extends AppCompatActivity { + + @SuppressLint("CommitPrefEdits") + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_main); + + Set stringSet = new HashSet<>(); + stringSet.add("SetOne"); + stringSet.add("SetTwo"); + stringSet.add("SetThree"); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + + SharedPreferences prefsOne = getSharedPreferences("countPrefOne", Context.MODE_PRIVATE); + SharedPreferences prefsTwo = getSharedPreferences("countPrefTwo", Context.MODE_PRIVATE); + + sharedPreferences.edit().putString("testOne", "one").commit(); + sharedPreferences.edit().putInt("testTwo", 2).commit(); + sharedPreferences.edit().putLong("testThree", 100000L).commit(); + sharedPreferences.edit().putFloat("testFour", 3.01F).commit(); + sharedPreferences.edit().putBoolean("testFive", true).commit(); + sharedPreferences.edit().putStringSet("testSix", stringSet).commit(); + + prefsOne.edit().putString("testOneNew", "one").commit(); + + prefsTwo.edit().putString("testTwoNew", "two").commit(); + + ContactDBHelper contactDBHelper = new ContactDBHelper(getApplicationContext()); + if (contactDBHelper.count() == 0) { + for (int i = 0; i < 100; i++) { + String name = "name_" + i; + String phone = "phone_" + i; + String email = "email_" + i; + String street = "street_" + i; + String place = "place_" + i; + contactDBHelper.insertContact(name, phone, email, street, place); + } + } + + CarDBHelper carDBHelper = new CarDBHelper(getApplicationContext()); + if (carDBHelper.count() == 0) { + for (int i = 0; i < 50; i++) { + String name = "name_" + i; + String color = "RED"; + float mileage = i + 10.45f; + carDBHelper.insertCar(name, color, mileage); + } + } + + ExtTestDBHelper extTestDBHelper = new ExtTestDBHelper(getApplicationContext()); + if (extTestDBHelper.count() == 0) { + for (int i = 0; i < 20; i++) { + String value = "value_" + i; + extTestDBHelper.insertTest(value); + } + } + + // Create Person encrypted database + PersonDBHelper personDBHelper = new PersonDBHelper(getApplicationContext()); + if (personDBHelper.count() == 0) { + for (int i = 0; i < 100; i++) { + String firstName = PersonDBHelper.PERSON_COLUMN_FIRST_NAME + "_" + i; + String lastName = PersonDBHelper.PERSON_COLUMN_LAST_NAME + "_" + i; + String address = PersonDBHelper.PERSON_COLUMN_ADDRESS + "_" + i; + personDBHelper.insertPerson(firstName, lastName, address); + } + } + + // Room database + UserDBHelper userDBHelper = new UserDBHelper(getApplicationContext()); + if (userDBHelper.count() == 0) { + List userList = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + User user = new User(); + user.id = (long) (i + 1); + user.name = "user_" + i; + userList.add(user); + } + userDBHelper.insertUser(userList); + } + + // Room inMemory database + if (userDBHelper.countInMemory() == 0) { + List userList = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + User user = new User(); + user.id = (long) (i + 1); + user.name = "in_memory_user_" + i; + userList.add(user); + } + userDBHelper.insertUserInMemory(userList); + } + + Utils.setCustomDatabaseFiles(getApplicationContext()); + Utils.setInMemoryRoomDatabases(userDBHelper.getInMemoryDatabase()); + } + + public void showDebugDbAddress(View view) { + Utils.showDebugDBAddressLogToast(getApplicationContext()); + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/CarDBHelper.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/CarDBHelper.java new file mode 100644 index 0000000..c620a4d --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/CarDBHelper.java @@ -0,0 +1,134 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.sample.encrypt.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; + +/** + * Created by amitshekhar on 06/02/17. + */ + +public class CarDBHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "Car.db"; + public static final String CARS_TABLE_NAME = "cars"; + public static final String CARS_COLUMN_ID = "id"; + public static final String CARS_COLUMN_NAME = "name"; + public static final String CARS_COLUMN_COLOR = "color"; + public static final String CCARS_COLUMN_MILEAGE = "mileage"; + + public CarDBHelper(Context context) { + super(context, DATABASE_NAME, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // TODO Auto-generated method stub + db.execSQL( + "create table cars " + + "(id integer primary key, name text, color text, mileage real)" + ); + + db.execSQL("create table [transaction] (id integer primary key, name text)"); + + for (int i = 0; i < 10; i++) { + db.execSQL("insert into [transaction] (name) values ('hello');"); + } + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // TODO Auto-generated method stub + db.execSQL("DROP TABLE IF EXISTS cars"); + onCreate(db); + } + + public boolean insertCar(String name, String color, float mileage) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put("name", name); + contentValues.put("color", color); + contentValues.put("mileage", mileage); + db.insert("cars", null, contentValues); + return true; + } + + public Cursor getData(int id) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor res = db.rawQuery("select * from cars where id=" + id + "", null); + return res; + } + + public int numberOfRows() { + SQLiteDatabase db = this.getReadableDatabase(); + int numRows = (int) DatabaseUtils.queryNumEntries(db, CARS_TABLE_NAME); + return numRows; + } + + public boolean updateCar(Integer id, String name, String color, float mileage) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put("name", name); + contentValues.put("color", color); + contentValues.put("mileage", mileage); + db.update("cars", contentValues, "id = ? ", new String[]{Integer.toString(id)}); + return true; + } + + public Integer deleteCar(Integer id) { + SQLiteDatabase db = this.getWritableDatabase(); + return db.delete("cars", + "id = ? ", + new String[]{Integer.toString(id)}); + } + + public ArrayList getAllCars() { + ArrayList arrayList = new ArrayList<>(); + + //hp = new HashMap(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor res = db.rawQuery("select * from cars", null); + res.moveToFirst(); + + while (!res.isAfterLast()) { + arrayList.add(res.getString(res.getColumnIndex(CARS_COLUMN_NAME))); + res.moveToNext(); + } + return arrayList; + } + + public int count() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery("select * from cars", null); + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + return 0; + } + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ContactDBHelper.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ContactDBHelper.java new file mode 100644 index 0000000..b0f4ef3 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ContactDBHelper.java @@ -0,0 +1,136 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.sample.encrypt.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; +import java.util.Calendar; + +/** + * Created by amitshekhar on 16/11/16. + */ +public class ContactDBHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "Contact.db"; + public static final String CONTACTS_TABLE_NAME = "contacts"; + public static final String CONTACTS_COLUMN_ID = "id"; + public static final String CONTACTS_COLUMN_NAME = "name"; + public static final String CONTACTS_COLUMN_EMAIL = "email"; + public static final String CONTACTS_COLUMN_STREET = "street"; + public static final String CONTACTS_COLUMN_CITY = "place"; + public static final String CONTACTS_COLUMN_PHONE = "phone"; + public static final String CONTACTS_CREATED_AT = "createdAt"; + + public ContactDBHelper(Context context) { + super(context, DATABASE_NAME, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // TODO Auto-generated method stub + db.execSQL( + "create table contacts " + + "(id integer primary key, name text, phone text, email text, street text, place text, createdAt integer)" + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // TODO Auto-generated method stub + db.execSQL("DROP TABLE IF EXISTS contacts"); + onCreate(db); + } + + public boolean insertContact(String name, String phone, String email, String street, String place) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put("name", name); + contentValues.put("phone", phone); + contentValues.put("email", email); + contentValues.put("street", street); + contentValues.put("place", place); + contentValues.put(CONTACTS_CREATED_AT, Calendar.getInstance().getTimeInMillis()); + db.insert("contacts", null, contentValues); + return true; + } + + public Cursor getData(int id) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor res = db.rawQuery("select * from contacts where id=" + id + "", null); + return res; + } + + public int numberOfRows() { + SQLiteDatabase db = this.getReadableDatabase(); + int numRows = (int) DatabaseUtils.queryNumEntries(db, CONTACTS_TABLE_NAME); + return numRows; + } + + public boolean updateContact(Integer id, String name, String phone, String email, String street, String place) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put("name", name); + contentValues.put("phone", phone); + contentValues.put("email", email); + contentValues.put("street", street); + contentValues.put("place", place); + db.update("contacts", contentValues, "id = ? ", new String[]{Integer.toString(id)}); + return true; + } + + public Integer deleteContact(Integer id) { + SQLiteDatabase db = this.getWritableDatabase(); + return db.delete("contacts", + "id = ? ", + new String[]{Integer.toString(id)}); + } + + public ArrayList getAllCotacts() { + ArrayList arrayList = new ArrayList<>(); + + //hp = new HashMap(); + SQLiteDatabase db = this.getReadableDatabase(); + Cursor res = db.rawQuery("select * from contacts", null); + res.moveToFirst(); + + while (!res.isAfterLast()) { + arrayList.add(res.getString(res.getColumnIndex(CONTACTS_COLUMN_NAME))); + res.moveToNext(); + } + return arrayList; + } + + public int count() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery("select * from contacts", null); + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + return 0; + } + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ExtTestDBHelper.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ExtTestDBHelper.java new file mode 100644 index 0000000..582a9f3 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/ExtTestDBHelper.java @@ -0,0 +1,92 @@ +package com.sample.encrypt.database; + +import android.content.ContentValues; +import android.content.Context; +import android.content.ContextWrapper; +import android.database.Cursor; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.io.File; +import java.util.Calendar; + +public class ExtTestDBHelper extends SQLiteOpenHelper { + + public static final String DIR_NAME = "custom_dir"; + public static final String DATABASE_NAME = "ExtTest.db"; + public static final String TEST_TABLE_NAME = "test"; + public static final String TEST_ID = "id"; + public static final String TEST_COLUMN_VALUE = "value"; + public static final String TEST_CREATED_AT = "createdAt"; + + public ExtTestDBHelper(Context context) { + super(new CustomDatabasePathContext(context), DATABASE_NAME, null, 1); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // TODO Auto-generated method stub + db.execSQL( + String.format( + "create table %s (%s integer primary key, %s text, %s integer)", + TEST_TABLE_NAME, + TEST_ID, + TEST_COLUMN_VALUE, + TEST_CREATED_AT + ) + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // TODO Auto-generated method stub + db.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE_NAME); + onCreate(db); + } + + public boolean insertTest(String value) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues contentValues = new ContentValues(); + contentValues.put("value", value); + contentValues.put(TEST_CREATED_AT, Calendar.getInstance().getTimeInMillis()); + db.insert(TEST_TABLE_NAME, null, contentValues); + return true; + } + + public int count() { + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery("select COUNT(*) from " + TEST_TABLE_NAME, null); + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + return 0; + } + } + + private static class CustomDatabasePathContext extends ContextWrapper { + + public CustomDatabasePathContext(Context base) { + super(base); + } + + @Override + public File getDatabasePath(String name) { + File databaseDir = new File(String.format("%s/%s", getFilesDir(), ExtTestDBHelper.DIR_NAME)); + databaseDir.mkdirs(); + File databaseFile = new File(String.format("%s/%s/%s", getFilesDir(), ExtTestDBHelper.DIR_NAME, name)); + return databaseFile; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { + return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { + return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); + } + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/PersonDBHelper.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/PersonDBHelper.java new file mode 100644 index 0000000..bb95c46 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/PersonDBHelper.java @@ -0,0 +1,136 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.sample.encrypt.database; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; + +import net.sqlcipher.DatabaseUtils; +import net.sqlcipher.database.SQLiteDatabase; +import net.sqlcipher.database.SQLiteOpenHelper; + +import java.util.ArrayList; + +public class PersonDBHelper extends SQLiteOpenHelper { + + public static final String DATABASE_NAME = "Person.db"; + public static final String PERSON_TABLE_NAME = "person"; + public static final String PERSON_COLUMN_ID = "id"; + public static final String PERSON_COLUMN_FIRST_NAME = "first_name"; + public static final String PERSON_COLUMN_LAST_NAME = "last_name"; + public static final String PERSON_COLUMN_ADDRESS = "address"; + private static final String DB_PASSWORD = "a_password"; + + public PersonDBHelper(Context context) { + + super(context, DATABASE_NAME, null, 1); + SQLiteDatabase.loadLibs(context); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL( + "create table person " + + "(id integer primary key, first_name text, last_name text, address text)" + ); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS person"); + onCreate(db); + } + + public boolean insertPerson(String firstName, String lastName, String address) { + SQLiteDatabase db = this.getWritableDatabase(DB_PASSWORD); + ContentValues contentValues = new ContentValues(); + contentValues.put("first_name", firstName); + contentValues.put("last_name", lastName); + contentValues.put("address", address); + db.insert("person", null, contentValues); + db.close(); + return true; + } + + public Cursor getData(int id) { + SQLiteDatabase db = this.getReadableDatabase(DB_PASSWORD); + Cursor res = db.rawQuery("select * from person where id=" + id + "", null); + return res; + } + + public int numberOfRows() { + SQLiteDatabase db = this.getReadableDatabase(DB_PASSWORD); + int numRows = (int) DatabaseUtils.queryNumEntries(db, PERSON_TABLE_NAME); + return numRows; + } + + public boolean updatePerson(Integer id, String firstName, String lastName, String address, float mileage) { + SQLiteDatabase db = this.getWritableDatabase(DB_PASSWORD); + ContentValues contentValues = new ContentValues(); + contentValues.put("first_name", firstName); + contentValues.put("last_name", lastName); + contentValues.put("address", address); + db.update("person", contentValues, "id = ? ", new String[]{Integer.toString(id)}); + db.close(); + return true; + } + + public Integer deletePerson(Integer id) { + SQLiteDatabase db = this.getWritableDatabase(DB_PASSWORD); + return db.delete("person", + "id = ? ", + new String[]{Integer.toString(id)}); + } + + public ArrayList getAllPerson() { + ArrayList arrayList = new ArrayList<>(); + + SQLiteDatabase db = this.getReadableDatabase(DB_PASSWORD); + Cursor res = db.rawQuery("select * from person", null); + res.moveToFirst(); + + while (!res.isAfterLast()) { + arrayList.add( + res.getString(res.getColumnIndex(PERSON_COLUMN_FIRST_NAME)) + " " + + res.getString(res.getColumnIndex(PERSON_COLUMN_LAST_NAME))); + res.moveToNext(); + } + res.close(); + db.close(); + return arrayList; + } + + public int count() { + SQLiteDatabase db = getReadableDatabase(DB_PASSWORD); + Cursor cursor = db.rawQuery("select * from person", null); + try { + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + return cursor.getInt(0); + } else { + return 0; + } + } finally { + cursor.close(); + db.close(); + } + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/AppDatabase.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/AppDatabase.java new file mode 100644 index 0000000..66917e6 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/AppDatabase.java @@ -0,0 +1,15 @@ +package com.sample.encrypt.database.room; + + +import androidx.room.Database; +import androidx.room.RoomDatabase; + +/** + * Created by anandgaurav on 12/02/18. + */ +@Database(entities = {User.class}, version = 1, exportSchema = false) +public abstract class AppDatabase extends RoomDatabase { + + public abstract UserDao userDao(); + +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/User.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/User.java new file mode 100644 index 0000000..8017511 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/User.java @@ -0,0 +1,17 @@ +package com.sample.encrypt.database.room; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +/** + * Created by anandgaurav on 12/02/18. + */ +@Entity(tableName = "users") +public class User { + + @PrimaryKey + public Long id; + + public String name; + +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDBHelper.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDBHelper.java new file mode 100644 index 0000000..34258a2 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDBHelper.java @@ -0,0 +1,47 @@ +package com.sample.encrypt.database.room; + +import android.content.Context; + +import androidx.room.Room; +import androidx.sqlite.db.SupportSQLiteDatabase; + +import java.util.List; + +/** + * Created by anandgaurav on 12/02/18. + */ + +public class UserDBHelper { + + private final AppDatabase appDatabase; + private final AppDatabase inMemoryAppDatabase; + + public UserDBHelper(Context context) { + appDatabase = Room.databaseBuilder(context, AppDatabase.class, "User.db") + .allowMainThreadQueries() + .build(); + inMemoryAppDatabase = Room.inMemoryDatabaseBuilder(context, AppDatabase.class) + .allowMainThreadQueries() + .build(); + } + + public void insertUser(List userList) { + appDatabase.userDao().insertAll(userList); + } + + public void insertUserInMemory(List userList) { + inMemoryAppDatabase.userDao().insertAll(userList); + } + + public int count() { + return appDatabase.userDao().loadAll().size(); + } + + public int countInMemory() { + return inMemoryAppDatabase.userDao().loadAll().size(); + } + + public SupportSQLiteDatabase getInMemoryDatabase() { + return inMemoryAppDatabase.getOpenHelper().getWritableDatabase(); + } +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDao.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDao.java new file mode 100644 index 0000000..2f80a99 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/database/room/UserDao.java @@ -0,0 +1,34 @@ +package com.sample.encrypt.database.room; + + +import androidx.room.Dao; +import androidx.room.Delete; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import java.util.List; + +/** + * Created by anandgaurav on 12/02/18. + */ + +@Dao +public interface UserDao { + + @Query("SELECT * FROM users") + List loadAll(); + + @Query("SELECT * FROM users WHERE id IN (:userIds)") + List loadAllByIds(List userIds); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(User user); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertAll(List users); + + @Delete + void delete(User user); + +} diff --git a/sample-app-encrypt/src/main/java/com/sample/encrypt/utils/Utils.java b/sample-app-encrypt/src/main/java/com/sample/encrypt/utils/Utils.java new file mode 100644 index 0000000..1c448a6 --- /dev/null +++ b/sample-app-encrypt/src/main/java/com/sample/encrypt/utils/Utils.java @@ -0,0 +1,92 @@ +/* + * + * * Copyright (C) 2019 Amit Shekhar + * * Copyright (C) 2011 Android Open Source Project + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package com.sample.encrypt.utils; + +import android.content.Context; +import android.util.Pair; +import android.widget.Toast; + +import androidx.sqlite.db.SupportSQLiteDatabase; + +import com.sample.encrypt.BuildConfig; +import com.sample.encrypt.database.ExtTestDBHelper; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.HashMap; + +/** + * Created by amitshekhar on 07/02/17. + */ + +public class Utils { + + private Utils() { + // This class is not publicly instantiable + } + + public static void showDebugDBAddressLogToast(Context context) { + if (BuildConfig.DEBUG) { + try { + Class debugDB = Class.forName("com.amitshekhar.DebugDB"); + Method getAddressLog = debugDB.getMethod("getAddressLog"); + Object value = getAddressLog.invoke(null); + Toast.makeText(context, (String) value, Toast.LENGTH_LONG).show(); + } catch (Exception ignore) { + + } + } + } + + public static void setCustomDatabaseFiles(Context context) { + if (BuildConfig.DEBUG) { + try { + Class debugDB = Class.forName("com.amitshekhar.DebugDB"); + Class[] argTypes = new Class[]{HashMap.class}; + Method setCustomDatabaseFiles = debugDB.getMethod("setCustomDatabaseFiles", argTypes); + HashMap> customDatabaseFiles = new HashMap<>(); + // set your custom database files + customDatabaseFiles.put(ExtTestDBHelper.DATABASE_NAME, + new Pair<>(new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME + + "/" + ExtTestDBHelper.DATABASE_NAME), "")); + setCustomDatabaseFiles.invoke(null, customDatabaseFiles); + } catch (Exception ignore) { + + } + } + } + + public static void setInMemoryRoomDatabases(SupportSQLiteDatabase... database) { + if (BuildConfig.DEBUG) { + try { + Class debugDB = Class.forName("com.amitshekhar.DebugDB"); + Class[] argTypes = new Class[]{HashMap.class}; + HashMap inMemoryDatabases = new HashMap<>(); + // set your inMemory databases + inMemoryDatabases.put("InMemoryOne.db", database[0]); + Method setRoomInMemoryDatabase = debugDB.getMethod("setInMemoryRoomDatabases", argTypes); + setRoomInMemoryDatabase.invoke(null, inMemoryDatabases); + } catch (Exception ignore) { + + } + } + } + +} diff --git a/sample-app-encrypt/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-app-encrypt/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/sample-app-encrypt/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/sample-app-encrypt/src/main/res/drawable/ic_launcher_background.xml b/sample-app-encrypt/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/sample-app-encrypt/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample-app-encrypt/src/main/res/layout/activity_main.xml b/sample-app-encrypt/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b5d361c --- /dev/null +++ b/sample-app-encrypt/src/main/res/layout/activity_main.xml @@ -0,0 +1,39 @@ + + + + +