1Panel's nginx and website services carry the highest activity risk

Across 11,164 functions in 1Panel-dev/1Panel, five critical-band functions spanning the nginx frontend tokenizer, website redirect parser, command executor, MySQL deleter, and Docker config writer are all in the 'fire' quadrant — structurally complex and actively changing in the last seven days.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk18.24Low
Hottest FunctiontokenBase

Antipatterns Detected

exit_heavy5god_function4deeply_nested3long_function3complex_branching2hub_function2middle_man1

Run this on your own codebase

See if your own repo has a tokenBase-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is an exit-heavy function and why does it matter in 1Panel?

An exit-heavy function has an unusually high number of distinct return or early-exit paths relative to its length — every `return err`, `return nil`, and `return buserr.New(...)` is a separate path a test must exercise to achieve meaningful coverage. In Go, error handling is explicit and idiomatic, which means complex functions naturally accumulate many early returns; exit-heavy is a signal that this accumulation has reached the point where branch coverage becomes burdensome. All five of 1Panel's top hotspots carry the exit-heavy pattern, which means a test suite that only hits the happy path is leaving the majority of the code's behaviour unverified. For a function like `delete` in `database_mysql.go`, which was touched four times in 30 days with a bug_fix_fraction of 0.75, incomplete test coverage of the exit paths is a concrete regression risk.

How do I reduce fan-out in Go?

Fan-out — the count of distinct functions a function directly calls — is reduced by grouping related collaborators behind a narrower interface or by extracting a cohesive sub-operation into its own function that encapsulates the calls it needs. A fan-out above 15 is a strong signal that the function is doing too many distinct things; above 25 it warrants immediate decomposition. For `GetRedirect` in `website.go`, with a fan-out of 32, the concrete first step is to extract the per-file parse-and-map logic into a `parseRedirectConfigFile(path string) (response.NginxRedirectConfig, error)` function — that alone moves roughly half the callees out of `GetRedirect`'s direct scope and gives you a unit-testable boundary around the nginx directive interpretation logic.

Is 1Panel actively maintained?

Yes — the quadrant data is unambiguous: all 3,184 at-risk functions sit in the fire quadrant, meaning every structurally complex function in the repository has seen recent commit activity. Among the top five hotspots specifically, `delete` in `database_mysql.go` has been touched 4 times in the last 30 days (last changed 4 days before the analysis commit), `GetRedirect` twice in 30 days (last changed 6 days prior), and `tokenBase`, `run`, and `UpdateConf` each once — all within 7 days of commit `e2754b4`. Active maintenance and structural complexity are not contradictions; the fire quadrant is precisely where they coincide, and it is what makes these functions live regression risks rather than backlog items.

How do I reproduce this analysis?

Clone `1Panel-dev/1Panel` and run `git checkout e2754b4` to pin to the analysed commit. Then run `hotspots analyze . --mode snapshot --explain-patterns --force` using the Hotspots CLI available at https://github.com/hotspots-dev/hotspots. The same command works on any local git repository without additional configuration — no `.hotspotsrc.json` is required to get started, though one can be added to exclude paths like generated code or vendored assets.

What does activity-weighted risk mean?

Activity-weighted risk multiplies a function's structural complexity score — derived from cyclomatic complexity, nesting depth, and fan-out — by a signal derived from how frequently the function has been touched in recent commits. The result means that a deeply complex function nobody has changed in two years scores lower than a moderately complex function being modified every few days, because the latter has a much higher near-term probability of introducing a regression. For 1Panel, this framing matters because all top hotspots are in the fire quadrant: the structural complexity is not sitting dormant, it is being actively edited, which is exactly when complexity converts into bugs.

At commit e2754b4, 1Panel-dev/1Panel has 11,164 analysed functions, 993 of which score in the critical band — and every single one of the top five hotspots lands in the ‘fire’ quadrant, meaning high structural complexity combined with active commits in the past week. The top-ranked function, tokenBase in frontend/src/components/codemirror-pro/nginx.ts, carries an activity-weighted risk score of 18.24, with a max nesting depth of 11 and a cyclomatic complexity of 22 — that combination of deep branching and a recent touch seven days ago makes it a live regression risk rather than a cleanup backlog item. I would treat these five functions as a single triage session rather than spreading review across the broader critical pool of 993.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
tokenBasefrontend/src/components/codemirror-pro/nginx.ts18.2221113
GetRedirectagent/app/service/website.go17.78932
runagent/utils/cmd/cmdx.go16.79427
deleteagent/app/service/database_mysql.go16.74322
UpdateConfagent/app/service/docker.go15.912615

Large Repo Analysis

1Panel is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Codemod / Tooling Files in Results

The top-ranked function tokenBase lives in frontend/src/components/codemirror-pro/nginx.ts. This is a custom CodeMirror StreamParser implementation bundled inside the 1Panel frontend source rather than a third-party package, so it is legitimate application code and not a vendored library. However, if your team maintains a local copy of a broader CodeMirror language definition here and does not intend to modify it, you can suppress it from future analyses by adding the following to .hotspotsrc.json: { "exclude": ["frontend/src/components/codemirror-pro/"] }. If the nginx tokenizer is actively maintained by the team — as the recent bug-fix commit suggests — leave it included.

1Panel is a Go-based server management panel with a Vue/TypeScript frontend; at the commit analysed it spans 11,164 functions, with 993 in the critical band and 3,184 sitting in the fire quadrant — complex code that is actively changing right now.

Quadrant distribution across 11,164 functions — every at-risk function is actively changing.
Fire3184Watch7980

11,164 functions analyzed

Detected Antipatterns
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
God Function×4God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Deeply Nested×3Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Long Function×3Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Complex Branching×2Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Hub Function×2Hub Function
Many other functions call this one — a change here ripples widely through callers.
Middle Man×1Middle Man
Mostly delegates to one other function without adding meaningful logic — a refactoring candidate for removal or consolidation.

The absence of any debt-quadrant functions is notable: there is no dormant complexity quietly accumulating — every structurally risky function is already being touched. That raises the stakes for each of the five functions below.


tokenBasefrontend/src/components/codemirror-pro/nginx.ts

tokenBase
frontend/src/components/codemirror-pro/nginx.ts
18.24
critical
CC 22
ND 11
FO 13
touches/30d 1

tokenBase is the character-level lexer for a custom nginx syntax highlighter built on CodeMirror’s StreamParser interface. Looking at the excerpt, it does everything a tokenizer needs to do in one body: it matches keywords, block keywords, important directives, @-prefixed meta tokens, comment markers, SGML comment entry, string delimiters, numeric units, selector operators, and structural punctuation — each branch delegating to a different return path or mutating state.tokenize to hand off to a helper. That design is correct for a streaming lexer, but it compresses what could be a dispatch table into a long if-else chain that reaches a max nesting depth of 11.

Max Nesting Depth 11
threshold: 4
Cyclomatic Complexity 22
threshold: 10

With 22 independent execution paths, there are 22 test cases needed for full branch coverage of this function alone. The exit_heavy and deeply_nested pattern flags reinforce that: many of the branches exit via return ret(...) immediately, which keeps the function from growing longer but doesn’t reduce the number of paths a reader — or a test suite — must track. The complex_branching flag signals that the branching structure itself is the primary complexity driver, not the depth of any single branch.

The file’s single commit in the last 30 days was a bug fix. That is not proof of a defect today, but it does suggest the file has attracted corrective attention and warrants scrutiny.

My recommendation: introduce a dispatch map from character or token type to a handler function. Each handler becomes independently testable, the nesting depth collapses dramatically, and adding a new nginx token type stops requiring a reader to mentally parse the entire if-else chain to find the right insertion point.


GetRedirectagent/app/service/website.go

GetRedirect
agent/app/service/website.go
17.69
critical
CC 8
ND 9
FO 32
touches/30d 2

GetRedirect reads nginx redirect configuration files for a given website from disk, parses each .conf or .bak file through an nginx config parser, and reconstructs structured NginxRedirectConfig response objects — including domain patterns, redirect targets, HTTP codes, and path-preservation flags. The source excerpt reveals the depth problem clearly: a for loop over directory entries nests into a file extension check, then into a config parse, then into a switch on the first directive name, then into a nested for over if-directive parameters, then into a for over child directives, then into a string suffix check. That path alone accounts for most of the nesting depth of 9.

Max Nesting Depth 9
threshold: 4
Fan-Out 32
threshold: 15

The fan-out of 32 is the most consequential number here. GetRedirect calls into the file operations layer, the repository layer, the nginx parser, string utilities, path utilities, and the response mapping layer — 32 distinct callees means a change to any one of those collaborators can silently change what this function produces. Combined with the god_function and long_function pattern flags, this reads as a function that has accumulated responsibility over time rather than being designed that way up front.

The external signals add context: one of the two commits in the last 30 days was a bug fix, and this function has a review-comment density of 7.0 — by far the highest in the top five. That density suggests reviewers are already finding things to question in this code. This function has been touched twice in the last 30 days, making it the most frequently changed of the top five alongside delete.

I would extract the per-file parsing logic — everything from reading the file through constructing the NginxRedirectConfig struct — into a dedicated parseRedirectConfigFile function. That immediately reduces the nesting in GetRedirect to the directory-iteration loop and moves the complex nginx-directive interpretation into a unit-testable scope.


runagent/utils/cmd/cmdx.go

run
agent/utils/cmd/cmdx.go
16.68
critical
CC 9
ND 4
FO 27
touches/30d 1

run is the central command-execution method on CommandHelper. From the excerpt, it handles timeout-vs-context setup, script-path-vs-direct invocation, process group assignment via SysProcAttr, and four distinct I/O routing modes — task item writer, logger writer, output file, or plain buffers — before starting the process and managing deferred cleanup of file and logger handles. Every one of those concerns is handled in the same function body.

Fan-Out 27
threshold: 15

The fan-out of 27 earns the hub_function and god_function flags together. run is the chokepoint through which every shell command the panel executes flows, so its coupling is broad by design — but that also means it is a high blast-radius target. The exit_heavy flag reflects Go’s idiomatic error-return style: the excerpt already shows two early return paths just in the cmd.Start() error block, and the full function has a cyclomatic complexity of 9, meaning at least 9 paths through the startup and I/O wiring logic alone.

In Go, a function like this sitting at the junction of context, exec, os, io, and syscall is also where concurrency surprises live. The SetPgid: true on SysProcAttr and the deferred cancel() call suggest the author has already thought about process lifecycle, but the more I/O routing modes accumulate, the harder it becomes to reason about what happens when, say, a logger writer blocks or a timeout fires mid-write.

Its single commit in the last 30 days was a bug fix. I would not restructure run wholesale, but I would extract the I/O routing logic — the four-way if/else if selecting between task writer, logger writer, file, and buffer — into a separate configureCommandIO function. That reduces the cognitive surface of run itself and makes the I/O modes independently testable.


deleteagent/app/service/database_mysql.go

delete
agent/app/service/database_mysql.go
16.68
critical
CC 4
ND 3
FO 22
touches/30d 4

delete is the internal MySQL database deletion handler on MysqlService. Its cyclomatic complexity of 4 is the lowest in the top five, and its nesting depth of 3 is well within readable range — so why does it rank this high? The answer is fan-out of 22 combined with 4 touches in 30 days, the highest touch frequency in the entire top five. This function has been changed four times in the last month.

Fan-Out 22
threshold: 15
Commits (30 days) 4
threshold: 2

The source excerpt shows the shape: load the delete target, run a dependency check, filter exclusions, verify no resources are still in use, acquire a client, delete the database, optionally purge backup directories and backup records, then remove the database row. That is a sequenced saga across the repository, filesystem, and live database client layers. The god_function and middle_man pattern flags reflect this: the function itself has modest branching but delegates heavily and touches many distinct subsystems. A change to any one of loadMysqlDeleteTarget, deleteCheck, LoadMysqlClientByFrom, cli.DeleteDatabase, backupRepo.DeleteRecord, or mysqlRepo.Delete has a direct path to changing what delete does.

Three of the four recent touches were tagged as bug fixes. That is the strongest historical defect signal in the top five and warrants careful review of each fix to confirm the saga’s error-handling invariants — particularly around the ForceDelete path, which swallows the DeleteDatabase error, and the silent _ = ignores on the cleanup calls.

Given the high touch frequency, I would add explicit integration tests covering the exclusion-filter path and the ForceDelete + DeleteBackup combination before the next change lands.


UpdateConfagent/app/service/docker.go

UpdateConf
agent/app/service/docker.go
15.93
critical
CC 12
ND 6
FO 15
touches/30d 1

UpdateConf modifies the Docker daemon’s daemon.json configuration file in place. The excerpt reveals a large switch on req.Key covering at least eight named cases — Registries, Mirrors, Ipv6, LogOption, LiveRestore, IPtables, Driver, http-proxy/https-proxy, socks5-proxy/close-proxy — each mutating a map[string]interface{} that is later marshalled back to disk. The Driver case alone nests three type assertions deep to find and patch a specific entry in an exec-opts array, which is where the nesting depth of 6 originates.

Cyclomatic Complexity 12
threshold: 10
Max Nesting Depth 6
threshold: 4

The complex_branching, deeply_nested, god_function, and long_function flags all fire here. With a cyclomatic complexity of 12, there are 12 execution paths that tests need to cover — and because each case modifies the same shared map, a missing delete call or an incorrect key string in one case can corrupt a Docker daemon configuration that only manifests on restart. The withRestart bool parameter suggests the caller controls whether the daemon is reloaded after the write, which means the blast radius of a bug here extends to Docker service availability.

Its single commit in the last 30 days was a bug fix — mirroring the pattern seen in tokenBase and run.

The most direct refactoring here is to extract each switch case into a dedicated applyXxxSetting(daemonMap map[string]interface{}, value string) function. That collapses UpdateConf to a dispatcher and makes each configuration type independently testable — you can verify that setting Driver to systemd produces the right exec-opts entry without having to construct the full SettingUpdate request path.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
exit_heavy5
god_function4
deeply_nested3
long_function3
complex_branching2
hub_function2
middle_man1

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/1Panel-dev/1Panel
cd 1Panel
git checkout e2754b447d6682cfee4968c19a00a026b8900c67
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses