diff --git a/module/webroot/index.html b/module/webroot/index.html
new file mode 100644
index 0000000..1bf7b0e
--- /dev/null
+++ b/module/webroot/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Acknowledgment to j-hc/zygisk-detach WebUI
+
+
+
\ No newline at end of file
diff --git a/module/webroot/index.js b/module/webroot/index.js
new file mode 100644
index 0000000..74dd041
--- /dev/null
+++ b/module/webroot/index.js
@@ -0,0 +1,108 @@
+let e = 0;
+const appTemplate = document.getElementById("app-template").content;
+const appListContainer = document.getElementById("apps-list");
+let excludeList = [];
+
+// Function to execute shell commands
+async function execCommand(command) {
+ return new Promise((resolve, reject) => {
+ const callbackName = `exec_callback_${Date.now()}_${e++}`;
+ window[callbackName] = (errno, stdout, stderr) => {
+ delete window[callbackName];
+ if (errno === 0) {
+ resolve(stdout);
+ } else {
+ console.error(`Error executing command: ${stderr}`);
+ reject(stderr);
+ }
+ };
+ try {
+ ksu.exec(command, "{}", callbackName);
+ } catch (error) {
+ console.error(`Execution error: ${error}`);
+ reject(error);
+ }
+ });
+}
+
+// Function to read the EXCLUDE file and return its contents as an array
+async function readExcludeFile() {
+ try {
+ const result = await execCommand('cat /data/adb/tricky_store/target_list_config/EXCLUDE');
+ excludeList = result.split("\n").filter(app => app.trim() !== ''); // Filter out empty lines
+ console.log("Current EXCLUDE list:", excludeList);
+ } catch (error) {
+ console.error("Failed to read EXCLUDE file:", error);
+ }
+}
+
+// Function to fetch the app list using the package manager
+async function fetchAppList() {
+ try {
+ const result = await execCommand("pm list packages -3 &1 | cat");
+ return result.split("\n").map(line => line.replace("package:", "").trim()).filter(Boolean);
+ } catch (error) {
+ console.error("Failed to fetch app list:", error);
+ return [];
+ }
+}
+
+// Function to render apps
+async function renderAppList() {
+ await readExcludeFile();
+ const apps = await fetchAppList();
+ apps.forEach(appName => {
+ const appElement = document.importNode(appTemplate, true);
+ appElement.querySelector(".name").textContent = appName;
+ const checkbox = appElement.querySelector(".checkbox");
+ checkbox.checked = !excludeList.includes(appName); // Deselect if in EXCLUDE
+ appListContainer.appendChild(appElement);
+ });
+}
+
+// Add button click event to update EXCLUDE file
+document.getElementById("add").addEventListener("click", async () => {
+ await readExcludeFile();
+ const deselectedApps = Array.from(appListContainer.querySelectorAll(".checkbox:not(:checked)"))
+ .map(checkbox => checkbox.closest(".card").querySelector(".name").textContent);
+ const selectedApps = Array.from(appListContainer.querySelectorAll(".checkbox:checked"))
+ .map(checkbox => checkbox.closest(".card").querySelector(".name").textContent);
+ // Add deselected apps to EXCLUDE if not already present
+ for (const app of deselectedApps) {
+ if (!excludeList.includes(app)) {
+ excludeList.push(app); // Add to the local list
+ console.log("Added to EXCLUDE list:", app);
+ } else {
+ console.log("App already in EXCLUDE file, skipping:", app);
+ }
+ }
+ // Remove selected apps from EXCLUDE
+ if (selectedApps.length > 0) {
+ selectedApps.forEach(app => {
+ excludeList = excludeList.filter(excludedApp => excludedApp !== app); // Remove from local list
+ console.log("Removed from EXCLUDE list:", app);
+ });
+ }
+ // Overwrite the EXCLUDE file with the updated list
+ try {
+ const updatedExcludeContent = excludeList.join("\n");
+ await execCommand(`echo "${updatedExcludeContent}" > /data/adb/tricky_store/target_list_config/EXCLUDE`);
+ console.log("EXCLUDE file updated successfully.");
+ } catch (error) {
+ console.error("Failed to update EXCLUDE file:", error);
+ }
+ await readExcludeFile();
+});
+
+// Search functionality
+document.getElementById("search").addEventListener("input", (e) => {
+ const searchQuery = e.target.value.toLowerCase();
+ const apps = appListContainer.querySelectorAll(".card");
+ apps.forEach(app => {
+ const name = app.querySelector(".name").textContent.toLowerCase();
+ app.style.display = name.includes(searchQuery) ? "block" : "none";
+ });
+});
+
+// Initial load
+renderAppList();
\ No newline at end of file
diff --git a/module/webroot/styles.css b/module/webroot/styles.css
new file mode 100644
index 0000000..96bf27c
--- /dev/null
+++ b/module/webroot/styles.css
@@ -0,0 +1,107 @@
+body {
+ background-color: #F5F5F5;
+}
+#apps-list {
+ margin-top: 70px;
+}
+.card {
+ border: none;
+ outline: none;
+ margin-bottom: 10px;
+ padding: 20px;
+ background-color: white;
+ border-radius: 8px;
+}
+.content {
+ justify-content: space-between;
+ align-items: center;
+ display: flex;
+}
+.name {
+ overflow-wrap: break-word;
+ word-break: break-word;
+ max-width: calc(100% - 30px);
+ margin: 0;
+ display: inline-block;
+}
+.checkbox {
+ margin-left: auto;
+}
+.floating-card {
+ display: flex;
+ justify-content: center;
+ z-index: 3;
+ position: fixed;
+ bottom: 45px;
+ left: 50%;
+ transform: translateX(-50%);
+}
+.floating-btn {
+ color: #fff;
+ cursor: pointer;
+ background-color: #007bff;
+ border: none;
+ border-radius: 24px;
+ justify-content: center;
+ align-items: center;
+ width: auto;
+ height: auto;
+ padding: 10px 20px;
+ font-size: 16px;
+ transition: transform .3s ease-in-out;
+ display: flex;
+ box-shadow: 0 4px 8px #0003;
+}
+.search-card {
+ position: fixed;
+ top: 10px;
+ width: calc(100% - 35px);
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 10px;
+ background-color: white;
+ border: 1px solid #ccc;
+ border-radius: 25px;
+ display: flex;
+ align-items: center;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+ z-index: 1000;
+}
+.search-input {
+ width: 100%;
+ border: none;
+ outline: none;
+ padding: 5px;
+ font-size: 14px;
+ background-color: #fff;
+ box-sizing: border-box;
+ text-align: left;
+}
+.acknowledgment {
+ width: auto;
+ background-color: #F5F5F5;
+ color: #6E6E6E;
+ text-align: center;
+ padding: 20px;
+ font-size: 14px;
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ color: #eee;
+ background-color: #121212;
+ }
+ .card {
+ background-color: #343434;
+ }
+ .search-card {
+ border: 1px solid #6E6E6E;
+ background-color: #343434;
+ }
+ .search-input {
+ color: #eee;
+ background-color: #343434;
+ }
+ .acknowledgment {
+ background-color: #121212;
+ }
+}
\ No newline at end of file