Skip to content

UI: Add dark/light theme toggle - #2656

Open
alanhasn wants to merge 18 commits into
MobSF:masterfrom
alanhasn:theme-toggle
Open

UI: Add dark/light theme toggle#2656
alanhasn wants to merge 18 commits into
MobSF:masterfrom
alanhasn:theme-toggle

Conversation

@alanhasn

Copy link
Copy Markdown

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 in localStorage.

  • Unified two previously-separate CSS token systems in mobsf-modern.css into one source of truth, then added a full light-mode override covering every token, including a dedicated --code-bg/--code-text pair for terminal/code-block surfaces that intentionally stay dark in both themes.
  • 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 using the same data-theme attribute and localStorage key for consistent behavior.
  • A toggle button (sun/moon icon) in the navbar, 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.
  • Converted ~150+ hardcoded hex/rgb colors across the Dynamic Analyzer dashboard, auth pages, and general pages (including SweetAlert2 dialog colors, which are JS options that don't pick up page CSS automatically) to theme-aware variables, so the whole app actually re-themes rather than just the shared layout.
  • Left real brand colors (Android green, Apple gray, PayPal/Twitter blue, etc.) and two full-screen dimming overlays (drag-drop target, loading spinner) fixed regardless of theme, since those are meant to stay constant.

Test plan

  • All touched templates compile and render via Django's template engine and test client
  • Manually verified the toggle in-browser across the dashboard, auth pages, error pages, and Dynamic Analyzer at both themes
  • Verified no flash-of-wrong-theme on reload after toggling

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a dark/light theme toggle

1 participant