UI: Add dark/light theme toggle - #2656
Open
alanhasn wants to merge 18 commits into
Open
Conversation
Replaces the old tabbed layout with a device-status sidebar, workflow
progress tracker, and live analysis console.
Also fixes several regressions the redesign had introduced:
- a stray extra {% endblock %} caused a 500 on every request
- ADB shell, Frida hooks panel, attach-to-process, activity/deeplink
tester and TLS tester were dropped from the markup while their JS
handlers stayed wired up; restored all of them in the new layout
- CodeMirror and the AJAX error handler pointed at removed element ids
- unescaped device/APK-controlled strings were inserted into innerHTML
in the log console (XSS); now escaped via safe DOM APIs
- Show Screen / Root CA / Proxy toggle buttons overwrote the action
card's icon+label structure instead of updating them individually
- removed a duplicate mobsf-modern.css <link> (already loaded globally)
dynamic_analysis.html: restored the #app_icon 64x64 sizing rule that
had been dropped, which was letting APK icons render at full size.
Redesigns login, registration, change-password and the user list with the dark theme used elsewhere. login.html was using a different class prefix (.login-*) than register/change_password (.auth-*) and each page carried its own ~250-370 line copy of near-identical card/input/ button CSS. Unified all three under .auth-* and extracted the byte-identical rules (card shell, brand, labels, inputs, error boxes, messages) into others/css/auth-pages.css; each page keeps only its own accent color and button treatment inline.
403/404/500 are standalone pages (they intentionally don't extend base_layout.html, so they keep rendering even if something else is broken) and each carried its own full copy of the same ~150-line error card design, differing only by accent color. Extracted the shared structure into others/css/error-pages.css, parameterized per page via --accent-code/--accent-code-rgb custom properties. general/error.html (the in-app analysis-error page) now uses the new components/error_card.html include instead of a hand-rolled inline version, and its action buttons moved from onmouseover/onmouseout handlers to plain CSS classes.
Applies the dark dashboard theme to base_layout.html, nav.html, home,
recent scans, tasks, view, zip, about and donate.
base_layout.html and home.html each defined their own copy of the
same ~28-variable :root token block (home.html's copy had already
drifted, missing 4 of the tokens). Moved the single canonical set into
others/css/mobsf-modern.css, which both pages already link, and
removed both inline duplicates.
Also:
- fixed recent.html's "items per page" dropdown, which only ever
rendered "5 per page" because {% for %} was slicing the literal
string "5,10,20,50,100,250" instead of iterating a list
- restored the "Information" sidebar tab label on the Compare Scans
page, which had been emptied out
- redesigned the global footer (was squeezed to 12px padding/font
with everything bold on one line); now uses flexbox spacing with a
readable copyright line and an accent-styled version badge
- removed 3 orphaned "shared" components (device_status_panel,
log_console, workflow_status) and ~500 lines of CSS that nothing
included, which also removed a .status-badge class collision with
the Dynamic Analyzer's own dashboard styles
- Removed zoom:0.85 from base_layout.html's body and home.html's html+body. Applying it in both places compounded (0.85*0.85), which is why home.html rendered visibly smaller than every other page; zoom is also non-standard and breaks how the browser reconciles real responsive layout. - Restructured nav.html to use Bootstrap's actual responsive navbar pattern (navbar-expand-sm + one shared .collapse.navbar-collapse wrapping both the nav links and the search/account group) instead of four separate flex children in the navbar that wrapped unpredictably on narrow screens. There is now a working mobile toggle for the nav links, which were previously just hidden below the sm breakpoint with no way to reach them. - Fixed the pushmenu hamburger (#nbar): it's meant to toggle the real AdminLTE sidebar, which is empty on most pages. Hiding it used to key off the sidebar-collapse body class via a timing-fragile inline script, but AdminLTE's own JS adds that same class dynamically on small screens for sidebar-mini pages that DO have real sidebar content - so it would wrongly hide the only way to reopen the sidebar there. Now keyed off whether a populated .nav-sidebar actually exists, via a plain CSS rule. - Added CSS to force the sidebar fully off-canvas by default on mobile and bring it on screen only via body.sidebar-open (the class AdminLTE's pushmenu toggle already manages), since the app was rendering the sidebar squished-but-visible on narrow phone widths instead of properly off-canvas.
- Switched from table-layout:fixed with percentage widths (which crushed every column into an unreadable overlapping mess on narrow screens) to fixed pixel widths with a min-width on the table, and fixed base_layout.html's .table-responsive override (overflow:hidden -> overflow-x:auto) so a table wider than its container actually scrolls horizontally on mobile instead of being clipped or force-squeezed. - On wide screens, the unconstrained "File" column used to absorb all leftover width, leaving most of the table visibly empty. Giving it a fixed width like the others fixes that. - Consolidated the Actions column's 5 buttons (PDF, Rescan, Download, Diff, Delete), which were split across two rows with inconsistent icon-only vs icon+text styling, into one centered row of equal-sized icon-only buttons with tooltips. - Scoped an overflow:visible override to this page's card: the "N / page" dropdown menu was rendering but getting clipped by .card's overflow:hidden (used elsewhere to clip rounded corners).
The App Scores / File Information / App Information (and, for iOS/ Windows binaries, Binary/XML Information) columns used bare Bootstrap column classes (col-2, col-6, col-4, col-3, col-5) with no responsive breakpoint. Bare col-N classes apply at every viewport width including mobile, so these never stacked - on a phone they stayed squeezed into their desktop proportions, wrapping text like "APP SCORES" one letter per line. Added col-12 col-md-N so they stack full-width below the md breakpoint and only sit side-by-side at tablet width and up. Applies to all five report templates: Android source/binary, iOS source/ binary, and Windows binary analysis.
The iOS Dynamic Analyzer button used Bootstrap's .dropdown-toggle class, whose automatic ::after caret becomes a flex item once base_layout.html's .btn rule makes buttons display:inline-flex - this combination was rendering the caret detached to the left of the button instead of after its text. Replaced the automatic caret with an explicit icon inside the button (play icon, text, chevron, all controlled flex children) and dropped the now-unnecessary dropdown-toggle class; the dropdown itself still opens via the data-toggle="dropdown" attribute, which Bootstrap's plugin binds to independently of that class.
Widened .auth-card from 460px to 640px and laid out Username/Email and Password/Confirm Password as two-column rows (auth-form-row) instead of a single narrow stacked column, so the extra width is actually used rather than just adding empty padding. Falls back to a single column below 520px.
Adds a working theme toggle, keyed off <html data-theme="light"> and persisted per-browser in localStorage: - Unified the two previously-separate token systems in mobsf-modern.css (the main --bg-*/--text-*/--accent-* set and a parallel --mobsf-* set that only .mobsf-error-card used) into one source of truth, then added a full light-mode override block (:root[data-theme="light"]) covering every token. .mobsf-error-card now uses the main tokens directly instead of its own palette. - Added a dedicated --code-bg/--code-text pair for terminal/code-block surfaces that should stay dark in both themes (matches how most code blocks/diffs behave even on light pages), rather than tying them to a token that flips with the theme. - 403/404/500 are standalone pages that don't load mobsf-modern.css by design (they must keep rendering even if something else breaks), so error-pages.css gets its own local light-mode override block using the same data-theme attribute and localStorage key for consistent behavior. - A toggle button (sun/moon icon) in nav.html, and matching buttons on the standalone error pages, wired up by others/js/theme-toggle.js. - A tiny inline script in <head>, before any stylesheet, applies the saved theme synchronously so the page never flashes the wrong theme on load.
For the toggle to actually change how a page looks, every surface color needs to come from a CSS variable rather than a literal hex/rgb value. Converted the pages that had drifted into hardcoding colors directly instead of using the shared tokens: - dynamic_analyzer.html: ~130 hardcoded hex colors across its whole dashboard (backgrounds, borders, text, accents) mapped to the matching tokens. Left the phone-screen mock (#000, an actual device screen) and an HTML entity (&MobSF#39;) untouched - verified none of the replacements landed inside the page's JS logic, only in <style> blocks and inline style attributes. - dynamic_analysis.html: the device-info-card gradient/border/text and status badges were hardcoded; now use the theme tokens (including the existing --grad-blue token, which already had a light variant). - change_password.html: the password-strength meter set its bar color via literal hex in JS; switched to passing var(--accent-*) strings, which the browser resolves the same as in CSS. - users.html/recent.html/tasks.html: SweetAlert2 dialogs configure their colors via JS options (background, color, confirmButtonColor, etc.), which don't pick up page CSS automatically - passed var(--*) strings instead of hex so the popups also re-theme. Also fixed recent.html's action-button hover text, hardcoded white, which would have had no contrast against the pale hover tint in light mode. - about.html/view.html/tasks.html: a few border colors were hardcoded rgba(48,54,61,...) instead of var(--border-subtle) (some were already using it as a var() fallback and needed no change). Left alone deliberately: real brand colors (Android green, Apple gray, PayPal/Twitter blue, etc.) on platform icons, which should stay fixed regardless of theme, and two full-screen dimming overlays (drag-drop target, loading spinner) that are meant to stay dark in both themes.
The confirmation dialog checked result.isConfirmed, which this project's bundled SweetAlert2 version never populates - every other Swal.fire() usage in the codebase (recent.html, dynamic_analysis.html) correctly checks result.value instead. Since the check always failed, confirming the dialog silently did nothing: no AJAX call, no error, no success message, and the user was never deleted.
form.is_valid() checks username uniqueness, but that's a time-of-check-time-of-use race: two near-simultaneous requests (e.g. a double-submitted form, since the button had no protection against it) can both pass validation before either commits, and the second one then hits an unhandled IntegrityError at the database level instead of the friendly "Please correct the error below" message. Wrapped form.save() to catch that and show a proper error, and disabled the submit button on submit so an accidental double-click can't trigger it in the first place.
.card had overflow:hidden, added to clip the card-header/card-body's square corners to the card's own border-radius. That also clips any Bootstrap dropdown-menu that opens inside a card and extends past its bottom edge - e.g. the iOS Dynamic Analyzer launch options on general/dynamic.html, and the Recent Scans page-size dropdown (patched locally before; that page-specific override is now redundant and removed). Replaced the overflow trick with rounding the header/body/footer's own corners directly (.card > .card-header:first-child gets top corners, .card > .card-body:last-child or .card-footer:last-child gets bottom corners, and a header-less card's lone .card-body gets both since it's first and last child at once). Same visual result, without clipping anything that needs to render outside the card's box.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2654
Stacked on #2655 - this branch builds on the dark theme redesign (the toggle needs its tokens to exist), so this diff currently includes those commits too. It'll shrink to just the toggle-specific commits once #2655 merges and this rebases onto master.
Summary
Adds a working dark/light theme toggle, keyed off
<html data-theme="light">and persisted per-browser inlocalStorage.mobsf-modern.cssinto one source of truth, then added a full light-mode override covering every token, including a dedicated--code-bg/--code-textpair for terminal/code-block surfaces that intentionally stay dark in both themes.mobsf-modern.cssby design (they must keep rendering even if something else breaks), soerror-pages.cssgets its own local light-mode override using the samedata-themeattribute andlocalStoragekey for consistent behavior.others/js/theme-toggle.js.<head>, before any stylesheet, applies the saved theme synchronously so the page never flashes the wrong theme on load.Test plan