Showing preview only (909K chars total). Download the full file or copy to clipboard to get everything.
Repository: GUI-for-Cores/GUI.for.SingBox
Branch: main
Commit: d3e2cfde49dd
Files: 219
Total size: 847.6 KB
Directory structure:
gitextract_dgpyt8y7/
├── .github/
│ └── workflows/
│ ├── release.yml
│ └── rolling-release.yml
├── .gitignore
├── GUI.for.SingBox.code-workspace
├── LICENSE
├── README.md
├── bridge/
│ ├── bridge.go
│ ├── exec.go
│ ├── exec_others.go
│ ├── exec_windows.go
│ ├── io.go
│ ├── mmdb.go
│ ├── net.go
│ ├── notification.go
│ ├── server.go
│ ├── tray.go
│ ├── types.go
│ └── utils.go
├── build/
│ ├── README.md
│ ├── darwin/
│ │ ├── Info.dev.plist
│ │ └── Info.plist
│ └── windows/
│ ├── info.json
│ └── wails.exe.manifest
├── frontend/
│ ├── .editorconfig
│ ├── .gitattributes
│ ├── .gitignore
│ ├── .oxfmtrc.json
│ ├── .oxlintrc.json
│ ├── .vscode/
│ │ ├── extensions.json
│ │ └── settings.json
│ ├── env.d.ts
│ ├── eslint.config.js
│ ├── index.html
│ ├── package.json
│ ├── src/
│ │ ├── App.vue
│ │ ├── api/
│ │ │ ├── kernel.ts
│ │ │ ├── request.ts
│ │ │ └── websocket.ts
│ │ ├── assets/
│ │ │ ├── globalMethods.ts
│ │ │ ├── logo.ts
│ │ │ ├── main.less
│ │ │ ├── polyfills.ts
│ │ │ └── styles/
│ │ │ ├── custom.less
│ │ │ ├── reset.less
│ │ │ ├── theme.less
│ │ │ ├── utilities/
│ │ │ │ ├── display.less
│ │ │ │ ├── flex.less
│ │ │ │ ├── gap.less
│ │ │ │ ├── grid.less
│ │ │ │ ├── index.less
│ │ │ │ ├── others.less
│ │ │ │ ├── rounded.less
│ │ │ │ ├── size.less
│ │ │ │ ├── spacing.less
│ │ │ │ └── text.less
│ │ │ └── variables.less
│ │ ├── bridge/
│ │ │ ├── app.ts
│ │ │ ├── exec.ts
│ │ │ ├── index.ts
│ │ │ ├── io.ts
│ │ │ ├── mmdb.ts
│ │ │ ├── net.ts
│ │ │ ├── notification.ts
│ │ │ ├── server.ts
│ │ │ └── wailsjs/
│ │ │ ├── go/
│ │ │ │ ├── bridge/
│ │ │ │ │ ├── App.d.ts
│ │ │ │ │ └── App.js
│ │ │ │ └── models.ts
│ │ │ └── runtime/
│ │ │ ├── package.json
│ │ │ ├── runtime.d.ts
│ │ │ └── runtime.js
│ │ ├── components/
│ │ │ ├── Button/
│ │ │ │ └── index.vue
│ │ │ ├── Card/
│ │ │ │ └── index.vue
│ │ │ ├── CheckBox/
│ │ │ │ └── index.vue
│ │ │ ├── CodeViewer/
│ │ │ │ └── index.vue
│ │ │ ├── ColorPicker/
│ │ │ │ └── index.vue
│ │ │ ├── Confirm/
│ │ │ │ └── index.vue
│ │ │ ├── CustomAction/
│ │ │ │ └── index.vue
│ │ │ ├── Divider/
│ │ │ │ └── index.vue
│ │ │ ├── Dropdown/
│ │ │ │ └── index.vue
│ │ │ ├── Empty/
│ │ │ │ └── index.vue
│ │ │ ├── Icon/
│ │ │ │ ├── icons.ts
│ │ │ │ └── index.vue
│ │ │ ├── Input/
│ │ │ │ └── index.vue
│ │ │ ├── InputList/
│ │ │ │ └── index.vue
│ │ │ ├── InterfaceSelect/
│ │ │ │ └── index.vue
│ │ │ ├── KeyValueEditor/
│ │ │ │ └── index.vue
│ │ │ ├── Menu/
│ │ │ │ └── index.vue
│ │ │ ├── Message/
│ │ │ │ └── index.vue
│ │ │ ├── Modal/
│ │ │ │ ├── index.ts
│ │ │ │ └── index.vue
│ │ │ ├── MultipleSelect/
│ │ │ │ └── index.vue
│ │ │ ├── Pagination/
│ │ │ │ └── index.vue
│ │ │ ├── Picker/
│ │ │ │ └── index.vue
│ │ │ ├── Progress/
│ │ │ │ └── index.vue
│ │ │ ├── Prompt/
│ │ │ │ └── index.vue
│ │ │ ├── Radio/
│ │ │ │ └── index.vue
│ │ │ ├── Select/
│ │ │ │ └── index.vue
│ │ │ ├── Switch/
│ │ │ │ └── index.vue
│ │ │ ├── Table/
│ │ │ │ └── index.vue
│ │ │ ├── Tabs/
│ │ │ │ └── index.vue
│ │ │ ├── Tag/
│ │ │ │ └── index.vue
│ │ │ ├── Tips/
│ │ │ │ └── index.vue
│ │ │ ├── TrafficChart/
│ │ │ │ └── index.vue
│ │ │ ├── _common/
│ │ │ │ ├── AboutView.vue
│ │ │ │ ├── CommandView.vue
│ │ │ │ ├── NavigationBar.vue
│ │ │ │ ├── SplashView.vue
│ │ │ │ └── TitleBar.vue
│ │ │ ├── components.d.ts
│ │ │ └── index.ts
│ │ ├── constant/
│ │ │ ├── app.ts
│ │ │ ├── kernel.ts
│ │ │ └── profile.ts
│ │ ├── directives/
│ │ │ ├── index.ts
│ │ │ ├── menu.ts
│ │ │ ├── platform.ts
│ │ │ └── tips.ts
│ │ ├── enums/
│ │ │ ├── app.ts
│ │ │ └── kernel.ts
│ │ ├── hooks/
│ │ │ ├── index.ts
│ │ │ ├── useBool.ts
│ │ │ └── useCoreBranch.ts
│ │ ├── lang/
│ │ │ ├── index.ts
│ │ │ └── locale/
│ │ │ ├── en.ts
│ │ │ └── zh.ts
│ │ ├── main.ts
│ │ ├── router/
│ │ │ ├── index.ts
│ │ │ ├── router.d.ts
│ │ │ └── routes.ts
│ │ ├── stores/
│ │ │ ├── app.ts
│ │ │ ├── appSettings.ts
│ │ │ ├── env.ts
│ │ │ ├── index.ts
│ │ │ ├── kernelApi.ts
│ │ │ ├── logs.ts
│ │ │ ├── plugins.ts
│ │ │ ├── profiles.ts
│ │ │ ├── rulesets.ts
│ │ │ ├── scheduledtasks.ts
│ │ │ └── subscribes.ts
│ │ ├── types/
│ │ │ ├── app.d.ts
│ │ │ ├── global.d.ts
│ │ │ ├── kernel.d.ts
│ │ │ ├── profile.d.ts
│ │ │ └── typescript.d.ts
│ │ ├── utils/
│ │ │ ├── command.ts
│ │ │ ├── completion.ts
│ │ │ ├── env.ts
│ │ │ ├── eventBus.ts
│ │ │ ├── format.ts
│ │ │ ├── generator.ts
│ │ │ ├── helper.ts
│ │ │ ├── index.ts
│ │ │ ├── interaction.ts
│ │ │ ├── is.ts
│ │ │ ├── migration.ts
│ │ │ ├── others.ts
│ │ │ ├── restorer.ts
│ │ │ └── tray.ts
│ │ └── views/
│ │ ├── HomeView/
│ │ │ ├── components/
│ │ │ │ ├── CommonController.vue
│ │ │ │ ├── ConnectionsController.vue
│ │ │ │ ├── GroupsController.vue
│ │ │ │ ├── KernelLogs.vue
│ │ │ │ ├── LogsController.vue
│ │ │ │ ├── OverView.vue
│ │ │ │ └── QuickStart.vue
│ │ │ └── index.vue
│ │ ├── PluginsView/
│ │ │ ├── components/
│ │ │ │ ├── PluginChangelog.vue
│ │ │ │ ├── PluginConfigItem.vue
│ │ │ │ ├── PluginConfigurator.vue
│ │ │ │ ├── PluginForm.vue
│ │ │ │ ├── PluginHub.vue
│ │ │ │ └── PluginView.vue
│ │ │ └── index.vue
│ │ ├── ProfilesView/
│ │ │ ├── components/
│ │ │ │ ├── DnsConfig.vue
│ │ │ │ ├── DnsRulesConfig.vue
│ │ │ │ ├── DnsServersConfig.vue
│ │ │ │ ├── GeneralConfig.vue
│ │ │ │ ├── InboundsConfig.vue
│ │ │ │ ├── MixinAndScriptConfig.vue
│ │ │ │ ├── OutboundsConfig.vue
│ │ │ │ ├── ProfileEditor.vue
│ │ │ │ ├── ProfileForm.vue
│ │ │ │ ├── RouteConfig.vue
│ │ │ │ ├── RouteRulesConfig.vue
│ │ │ │ └── RouteRulesetConfig.vue
│ │ │ └── index.vue
│ │ ├── RulesetsView/
│ │ │ ├── components/
│ │ │ │ ├── RulesetForm.vue
│ │ │ │ ├── RulesetHub.vue
│ │ │ │ └── RulesetView.vue
│ │ │ └── index.vue
│ │ ├── ScheduledTasksView/
│ │ │ ├── components/
│ │ │ │ ├── ScheduledTaskForm.vue
│ │ │ │ └── ScheduledTasksLogs.vue
│ │ │ └── index.vue
│ │ ├── SettingsView/
│ │ │ ├── components/
│ │ │ │ ├── CoreSettings.vue
│ │ │ │ ├── GeneralSettings.vue
│ │ │ │ ├── PluginSettings.vue
│ │ │ │ └── components/
│ │ │ │ ├── AdvancedSettings.vue
│ │ │ │ ├── BehaviorSettings.vue
│ │ │ │ ├── BranchDetail.vue
│ │ │ │ ├── CoreConfig.vue
│ │ │ │ ├── FeatureSettings.vue
│ │ │ │ ├── PersonalizationSettings.vue
│ │ │ │ ├── SwitchBranch.vue
│ │ │ │ └── SystemProxySettings.vue
│ │ │ └── index.vue
│ │ └── SubscribesView/
│ │ ├── components/
│ │ │ ├── ProxiesEditor.vue
│ │ │ ├── ProxiesView.vue
│ │ │ ├── SubscribeForm.vue
│ │ │ └── SubscribeScript.vue
│ │ └── index.vue
│ ├── tsconfig.app.json
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ └── vite.config.ts
├── go.mod
├── go.sum
├── main.go
└── wails.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/release.yml
================================================
name: Build GUI.for.SingBox
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
Build-Frontend:
runs-on: ubuntu-latest
if: github.repository == 'GUI-for-Cores/GUI.for.SingBox'
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v6
with:
node-version: "latest"
cache: "pnpm"
cache-dependency-path: frontend/pnpm-lock.yaml
- run: |
cd frontend
pnpm install --frozen-lockfile
pnpm build-only
- uses: actions/upload-artifact@v6
with:
name: frontend-dist
path: frontend/dist
Build-Windows:
needs: Build-Frontend
runs-on: windows-latest
env:
APP_NAME: GUI.for.SingBox
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- uses: actions/download-artifact@v8
with:
name: frontend-dist
path: frontend/dist
- name: Build & Pack Windows
shell: pwsh
run: |
function Build-And-Pack {
param([string]$arch)
$env:GOOS="windows"
$env:GOARCH=$arch
Write-Host "==> Building Windows $arch..."
~/go/bin/wails build -m -s -trimpath -skipbindings -devtools -tags webkit2_41 -o "$env:APP_NAME.exe"
cd build/bin
$zipName = "$env:APP_NAME-windows-$arch.zip"
Compress-Archive -Path "$env:APP_NAME.exe" -DestinationPath $zipName -Force
cd ../..
}
$arches = @("amd64","arm64","386")
foreach ($arch in $arches) { Build-And-Pack $arch }
- uses: actions/upload-artifact@v6
with:
name: windows-builds
path: build/bin/*.zip
Build-macOS:
needs: Build-Frontend
runs-on: macos-latest
env:
APP_NAME: GUI.for.SingBox
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- uses: actions/download-artifact@v8
with:
name: frontend-dist
path: frontend/dist
- run: |
go mod vendor
sed -i "" "s/\[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular\]/[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]/g" vendor/github.com/wailsapp/wails/v2/internal/frontend/desktop/darwin/AppDelegate.m
- name: Build & Pack macOS
run: |
build_and_pack() {
arch=$1
export GOOS=darwin GOARCH=$arch
echo "==> Building macOS $arch..."
~/go/bin/wails build -m -s -trimpath -skipbindings -devtools -tags webkit2_41 -o $APP_NAME.exe
cd build/bin
zip -q -r $APP_NAME-darwin-$arch.zip $APP_NAME.app
cd ../..
}
for arch in amd64 arm64; do build_and_pack $arch; done
- uses: actions/upload-artifact@v6
with:
name: macos-builds
path: build/bin/*.zip
Build-Linux:
needs: Build-Frontend
runs-on: ubuntu-latest
env:
APP_NAME: GUI.for.SingBox
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
check-latest: true
- run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
- uses: actions/download-artifact@v8
with:
name: frontend-dist
path: frontend/dist
- run: |
sudo apt-get update
sudo apt-get install libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build & Pack Linux
run: |
build_and_pack() {
arch=$1
export GOOS=linux GOARCH=$arch
echo "==> Building Linux $arch..."
~/go/bin/wails build -m -s -trimpath -skipbindings -devtools -tags webkit2_41 -o $APP_NAME.exe
cd build/bin
mv $APP_NAME.exe $APP_NAME
zip $APP_NAME-linux-$arch.zip $APP_NAME
cd ../..
}
build_and_pack amd64
- uses: actions/upload-artifact@v6
with:
name: linux-builds
path: build/bin/*.zip
Release:
needs: [Build-Windows, Build-macOS, Build-Linux]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v8
with:
path: release-assets
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
files: release-assets/**/*.zip
draft: false
prerelease: ${{ contains(github.ref_name, 'dev') }}
body: |
Auto-generated release from GitHub Actions.
================================================
FILE: .github/workflows/rolling-release.yml
================================================
name: Rolling Release
on:
push:
branches: [main]
paths:
- "frontend/**"
workflow_dispatch:
jobs:
Build:
permissions: write-all
runs-on: ubuntu-latest
if: github.repository == 'GUI-for-Cores/GUI.for.SingBox'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "latest"
cache: "pnpm"
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install dependencies
run: cd frontend && pnpm install --frozen-lockfile
- name: Build Frontend
run: cd frontend && pnpm build-only
- name: Create a compressed file
run: |
git rev-parse --short HEAD | tr -d '\n' > frontend/dist/version.txt
cd frontend
mv dist rolling-release
zip -r rolling-release.zip rolling-release
- name: Generate Changelog
run: |
set +e
LAST_COMMIT=$(curl -L https://github.com/GUI-for-Cores/GUI.for.SingBox/releases/download/rolling-release/version.txt)
echo -e "## Change log\n\n> Update time: $(TZ='Asia/Shanghai' date "+%Y-%m-%d %H:%M:%S")\n" > changelog.md
git log $LAST_COMMIT..HEAD --pretty=format:"* %s" >> changelog.md
if [ $? -ne 0 ]; then
echo "No changes found since last commit." >> changelog.md
fi
set -e
- name: Create Release and Upload Assets
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./{frontend/{rolling-release.zip,rolling-release/version.txt},changelog.md}
file_glob: true
tag: rolling-release
release_name: rolling-release
overwrite: true
draft: false
prerelease: true
body: |
Rolling release built by GitHub Actions.
To use this version, please install the "Rolling Release Assistant" plugin and enable "Enable Rolling Release" within the app.
================================================
FILE: .gitignore
================================================
build/bin
frontend/dist
.DS_Store
================================================
FILE: GUI.for.SingBox.code-workspace
================================================
{
"folders": [
{
"path": "."
},
{
"path": "frontend"
}
],
"settings": {
"oxc.enable": true
}
}
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
<div align="center">
<img src="build/appicon.png" alt="GUI.for.SingBox" width="200">
<h1>GUI.for.SingBox</h1>
<p>A GUI program developed by vue3 + wails.</p>
</div>
## Preview
Take a look at the live version here: 👉 <a href="https://gui-for-cores.github.io/guide/gfs/" target="_blank">Live Demo</a>
<div align="center">
<img src="docs/imgs/light.png">
</div>
## Document
[Community](https://gui-for-cores.github.io/guide/gfs/community)
## Build
1、Build Environment
- Node.js [link](https://nodejs.org/en)
- pnpm :`npm i -g pnpm`
- Go [link](https://go.dev/)
- Wails [link](https://wails.io/) :`go install github.com/wailsapp/wails/v2/cmd/wails@latest`
2、Pull and Build
```bash
git clone https://github.com/GUI-for-Cores/GUI.for.SingBox.git
cd GUI.for.SingBox/frontend
pnpm install --frozen-lockfile && pnpm build
cd ..
wails build
```
## Stargazers over time
[](https://starchart.cc/GUI-for-Cores/GUI.for.SingBox)
================================================
FILE: bridge/bridge.go
================================================
package bridge
import (
"embed"
"log"
"net"
"os"
"os/exec"
"os/user"
"path/filepath"
"slices"
"strings"
sysruntime "runtime"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/menu/keys"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/runtime"
"gopkg.in/yaml.v3"
)
var Config = &AppConfig{}
var Env = &EnvResult{
IsStartup: true,
PreventExit: true,
FromTaskSch: false,
WebviewPath: "",
AppName: "",
AppVersion: "v1.21.0",
BasePath: "",
OS: sysruntime.GOOS,
ARCH: sysruntime.GOARCH,
IsPrivileged: false,
}
// NewApp creates a new App application struct
func NewApp() *App {
return &App{
AppMenu: menu.NewMenu(),
}
}
func CreateApp(fs embed.FS) *App {
exePath, err := os.Executable()
if err != nil {
panic(err)
}
Env.BasePath = filepath.ToSlash(filepath.Dir(exePath))
Env.AppName = filepath.Base(exePath)
if slices.Contains(os.Args, "tasksch") {
Env.FromTaskSch = true
}
if priv, err := IsPrivileged(); err == nil {
Env.IsPrivileged = priv
}
app := NewApp()
if Env.OS == "darwin" {
createMacOSSymlink()
createMacOSMenus(app)
}
if Env.OS == "windows" {
processFixedWebView2Runtime()
}
extractEmbeddedFiles(fs)
loadConfig()
return app
}
func (a *App) IsStartup() bool {
if Env.IsStartup {
Env.IsStartup = false
return true
}
return false
}
func (a *App) ExitApp() {
log.Printf("ExitApp")
Env.PreventExit = false
runtime.Quit(a.Ctx)
}
func (a *App) RestartApp() FlagResult {
log.Printf("RestartApp")
exePath := Env.BasePath + "/" + Env.AppName
cmd := exec.Command(exePath)
SetCmdWindowHidden(cmd)
if err := cmd.Start(); err != nil {
return FlagResult{false, err.Error()}
}
a.ExitApp()
return FlagResult{true, "Success"}
}
func (a *App) GetEnv() EnvResult {
log.Printf("GetEnv")
return EnvResult{
AppName: Env.AppName,
AppVersion: Env.AppVersion,
BasePath: Env.BasePath,
OS: Env.OS,
ARCH: Env.ARCH,
IsPrivileged: Env.IsPrivileged,
}
}
func (a *App) GetInterfaces() FlagResult {
log.Printf("GetInterfaces")
interfaces, err := net.Interfaces()
if err != nil {
return FlagResult{false, err.Error()}
}
var interfaceNames []string
for _, inter := range interfaces {
interfaceNames = append(interfaceNames, inter.Name)
}
return FlagResult{true, strings.Join(interfaceNames, "|")}
}
func (a *App) ShowMainWindow() {
log.Printf("ShowMainWindow")
runtime.WindowShow(a.Ctx)
}
func createMacOSSymlink() {
user, _ := user.Current()
linkPath := Env.BasePath + "/data"
appPath := "/Users/" + user.Username + "/Library/Application Support/" + Env.AppName
os.MkdirAll(appPath, os.ModePerm)
os.Symlink(appPath, linkPath)
}
func createMacOSMenus(app *App) {
appMenu := app.AppMenu.AddSubmenu("App")
appMenu.AddText("Show", keys.CmdOrCtrl("s"), func(_ *menu.CallbackData) {
runtime.WindowShow(app.Ctx)
})
appMenu.AddText("Hide", keys.CmdOrCtrl("h"), func(_ *menu.CallbackData) {
runtime.WindowHide(app.Ctx)
})
appMenu.AddSeparator()
appMenu.AddText("Quit", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
runtime.EventsEmit(app.Ctx, "onExitApp")
})
// on macos platform, we should append EditMenu to enable Cmd+C,Cmd+V,Cmd+Z... shortcut
app.AppMenu.Append(menu.EditMenu())
}
func processFixedWebView2Runtime() {
webviewDir := filepath.Join(Env.BasePath, "data", "WebView2")
err := filepath.Walk(webviewDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() && strings.EqualFold(info.Name(), "msedgewebview2.exe") {
Env.WebviewPath = filepath.Dir(path)
log.Printf("WebView2 runtime already exists at: %s", Env.WebviewPath)
return filepath.SkipDir
}
return nil
})
if err != nil {
log.Printf("Error during recursive search: %v\n", err)
return
}
if Env.WebviewPath != "" {
return
}
entries, err := os.ReadDir(webviewDir)
if err != nil {
log.Printf("Failed to read directory: %v\n", err)
return
}
var cabFile string
for _, e := range entries {
if !e.IsDir() &&
strings.HasSuffix(strings.ToLower(e.Name()), ".cab") &&
strings.Contains(e.Name(), "Microsoft.WebView2.FixedVersionRuntime") {
cabFile = filepath.Join(webviewDir, e.Name())
break
}
}
if cabFile == "" {
log.Println("No WebView2 .cab file found. Skipping extraction.")
return
}
log.Printf("Found CAB file: %s\n", cabFile)
cmd := exec.Command("expand.exe", "-F:*", cabFile, webviewDir)
SetCmdWindowHidden(cmd)
log.Println("Extracting WebView2 Runtime...")
if err := cmd.Run(); err != nil {
log.Printf("Extraction failed: %v\n", err)
return
}
log.Printf("WebView2 Runtime extracted successfully into: %s\n", webviewDir)
Env.WebviewPath = strings.TrimSuffix(cabFile, ".cab")
}
func extractEmbeddedFiles(fs embed.FS) {
iconSrc := "frontend/dist/icons"
iconDst := "data/.cache/icons"
imgSrc := "frontend/dist/imgs"
imgDst := "data/.cache/imgs"
os.MkdirAll(GetPath(iconDst), os.ModePerm)
os.MkdirAll(GetPath(imgDst), os.ModePerm)
extractFiles(fs, iconSrc, iconDst)
extractFiles(fs, imgSrc, imgDst)
}
func extractFiles(fs embed.FS, srcDir, dstDir string) {
files, _ := fs.ReadDir(srcDir)
for _, file := range files {
fileName := file.Name()
dstPath := GetPath(dstDir + "/" + fileName)
if _, err := os.Stat(dstPath); os.IsNotExist(err) {
log.Printf("InitResources [%s]: %s", dstDir, fileName)
data, _ := fs.ReadFile(srcDir + "/" + fileName)
if err := os.WriteFile(dstPath, data, os.ModePerm); err != nil {
log.Printf("Error writing file %s: %v", dstPath, err)
}
}
}
}
func loadConfig() {
b, err := os.ReadFile(Env.BasePath + "/data/user.yaml")
if err == nil {
yaml.Unmarshal(b, &Config)
}
if Config.Width == 0 {
Config.Width = 800
}
if Config.Height == 0 {
Config.Height = 540
}
Config.StartHidden = Env.FromTaskSch && Config.WindowStartState == int(options.Minimised)
if !Env.FromTaskSch {
Config.WindowStartState = int(options.Normal)
}
}
================================================
FILE: bridge/exec.go
================================================
package bridge
import (
"bufio"
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/shirou/gopsutil/v3/process"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
func (a *App) Exec(path string, args []string, options ExecOptions) FlagResult {
log.Printf("Exec: %s %s %v", path, args, options)
exePath := GetPath(path)
if _, err := os.Stat(exePath); os.IsNotExist(err) {
exePath = path
}
cmd := exec.Command(exePath, args...)
SetCmdWindowHidden(cmd)
cmd.Dir = options.WorkingDirectory
cmd.Env = os.Environ()
for key, value := range options.Env {
cmd.Env = append(cmd.Env, key+"="+value)
}
out, err := cmd.CombinedOutput()
var output string
if options.Convert {
output = strings.TrimSpace(ConvertByte2String(out))
} else {
output = strings.TrimSpace(string(out))
}
if err != nil {
if output == "" {
output = err.Error()
}
return FlagResult{false, output}
}
return FlagResult{true, output}
}
func (a *App) ExecBackground(path string, args []string, outEvent string, endEvent string, options ExecOptions) FlagResult {
log.Printf("ExecBackground: %s %s %s %s %v", path, args, outEvent, endEvent, options)
exePath := GetPath(path)
pidPath := ""
if _, err := os.Stat(exePath); os.IsNotExist(err) {
exePath = path
}
if options.PidFile != "" {
pidPath = GetPath(options.PidFile)
}
cmd := exec.Command(exePath, args...)
SetCmdWindowHidden(cmd)
cmd.Dir = options.WorkingDirectory
cmd.Env = os.Environ()
for key, value := range options.Env {
cmd.Env = append(cmd.Env, key+"="+value)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return FlagResult{false, err.Error()}
}
cmd.Stderr = cmd.Stdout
if err := cmd.Start(); err != nil {
return FlagResult{false, err.Error()}
}
pid := strconv.Itoa(cmd.Process.Pid)
if pidPath != "" {
err := os.WriteFile(pidPath, []byte(pid), os.ModePerm)
if err != nil {
_ = SendExitSignal(cmd.Process)
_ = waitForProcessExitWithTimeout(cmd.Process, 10)
return FlagResult{false, err.Error()}
}
}
if outEvent != "" {
scanAndEmit := func(reader io.Reader) {
scanner := bufio.NewScanner(reader)
stopOutput := false
for scanner.Scan() {
var text string
if options.Convert {
text = ConvertByte2String(scanner.Bytes())
} else {
text = scanner.Text()
}
if !stopOutput {
runtime.EventsEmit(a.Ctx, outEvent, text)
if options.StopOutputKeyword != "" && strings.Contains(text, options.StopOutputKeyword) {
stopOutput = true
}
}
}
}
go scanAndEmit(stdout)
}
if endEvent != "" {
go func() {
cmd.Wait()
if pidPath != "" {
_ = os.Remove(pidPath)
}
runtime.EventsEmit(a.Ctx, endEvent)
}()
}
return FlagResult{true, pid}
}
func (a *App) ProcessInfo(pid int32) FlagResult {
log.Printf("ProcessInfo: %d", pid)
proc, err := process.NewProcess(pid)
if err != nil {
return FlagResult{false, err.Error()}
}
name, err := proc.Name()
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, name}
}
func (a *App) ProcessMemory(pid int32) FlagResult {
log.Printf("ProcessMemory: %d", pid)
proc, err := process.NewProcess(pid)
if err != nil {
return FlagResult{false, err.Error()}
}
memInfo, err := proc.MemoryInfo()
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, strconv.FormatUint(memInfo.RSS, 10)}
}
func (a *App) KillProcess(pid int, timeout int) FlagResult {
log.Printf("KillProcess: %d %d", pid, timeout)
process, err := os.FindProcess(pid)
if err != nil {
return FlagResult{false, err.Error()}
}
if err := SendExitSignal(process); err != nil {
log.Printf("SendExitSignal Err: %s", err.Error())
}
if err := waitForProcessExitWithTimeout(process, timeout); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func waitForProcessExitWithTimeout(process *os.Process, timeoutSeconds int) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds)*time.Second)
defer cancel()
interval := 10 * time.Millisecond
maxInterval := 1000 * time.Millisecond
for {
select {
case <-ctx.Done():
if killErr := process.Kill(); killErr != nil {
return fmt.Errorf("timed out after %d seconds waiting for process %d, and failed to kill it: %w", timeoutSeconds, process.Pid, killErr)
}
return nil
default:
alive, err := IsProcessAlive(process)
if err != nil {
return fmt.Errorf("failed to check status of process %d: %w", process.Pid, err)
}
if !alive {
return nil
}
time.Sleep(interval)
interval = min(time.Duration(interval*2), maxInterval)
}
}
}
================================================
FILE: bridge/exec_others.go
================================================
//go:build !windows
package bridge
import (
"errors"
"fmt"
"os"
"os/exec"
"syscall"
)
func SetCmdWindowHidden(cmd *exec.Cmd) {
}
func SendExitSignal(p *os.Process) error {
return p.Signal(syscall.SIGINT)
}
func IsProcessAlive(p *os.Process) (bool, error) {
err := p.Signal(syscall.Signal(0))
if err == nil {
return true, nil
}
if errors.Is(err, os.ErrProcessDone) {
return false, nil
}
if errno, ok := err.(syscall.Errno); ok {
switch errno {
case syscall.ESRCH:
return false, nil
case syscall.EPERM:
return true, nil
}
}
return false, fmt.Errorf("failed to check process %d: %w", p.Pid, err)
}
func IsPrivileged() (bool, error) {
return os.Geteuid() == 0, nil
}
================================================
FILE: bridge/exec_windows.go
================================================
//go:build windows
package bridge
import (
"fmt"
"os"
"os/exec"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const ATTACH_PARENT_PROCESS uintptr = ^uintptr(0)
var (
modAdvapi32 = windows.NewLazySystemDLL("advapi32.dll")
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
procCheckTokenMembership = modAdvapi32.NewProc("CheckTokenMembership")
procFreeConsole = modKernel32.NewProc("FreeConsole")
procAttachConsole = modKernel32.NewProc("AttachConsole")
procSetConsoleCtrlHandler = modKernel32.NewProc("SetConsoleCtrlHandler")
procGenerateConsoleCtrlEvent = modKernel32.NewProc("GenerateConsoleCtrlEvent")
)
func SetCmdWindowHidden(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: windows.CREATE_UNICODE_ENVIRONMENT | windows.CREATE_NEW_PROCESS_GROUP,
HideWindow: true,
}
}
func SendExitSignal(p *os.Process) error {
if ret, _, err := procFreeConsole.Call(); ret == 0 && err != windows.ERROR_INVALID_HANDLE {
return err
}
defer func() {
procAttachConsole.Call(ATTACH_PARENT_PROCESS)
}()
if ret, _, err := procAttachConsole.Call(uintptr(p.Pid)); ret == 0 && err != windows.ERROR_ACCESS_DENIED {
return err
}
if ret, _, err := procSetConsoleCtrlHandler.Call(0, 1); ret == 0 {
return err
}
if ret, _, err := procGenerateConsoleCtrlEvent.Call(windows.CTRL_BREAK_EVENT, uintptr(p.Pid)); ret == 0 {
return err
}
return nil
}
func IsProcessAlive(p *os.Process) (bool, error) {
h, err := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(p.Pid))
if err != nil {
if err == windows.ERROR_INVALID_PARAMETER {
return false, nil
}
return false, err
}
defer windows.CloseHandle(h)
s, err := windows.WaitForSingleObject(h, 0)
if err != nil {
return false, err
}
switch s {
case windows.WAIT_OBJECT_0:
return false, nil
case uint32(windows.WAIT_TIMEOUT):
return true, nil
default:
return false, fmt.Errorf("unexpected WaitForSingleObject status: %d", s)
}
}
func IsPrivileged() (bool, error) {
var sid *windows.SID
sid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
if err != nil {
return false, err
}
var isMember int32
ret, _, err := procCheckTokenMembership.Call(0, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(&isMember)))
if ret == 0 {
return false, err
}
return isMember != 0, nil
}
================================================
FILE: bridge/io.go
================================================
package bridge
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"encoding/base64"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/pkg/browser"
)
const (
Binary = "Binary"
Text = "Text"
)
func (a *App) WriteFile(path string, content string, options IOOptions) FlagResult {
log.Printf("WriteFile [%s %s]: %s", options.Mode, options.Range, path)
fullPath := GetPath(path)
if err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm); err != nil {
return FlagResult{false, err.Error()}
}
var data []byte
var err error
switch options.Mode {
case Text:
data = []byte(content)
case Binary:
data, err = base64.StdEncoding.DecodeString(content)
if err != nil {
return FlagResult{false, err.Error()}
}
default:
return FlagResult{false, "Unsupported IO mode: " + options.Mode}
}
file, err := os.OpenFile(fullPath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return FlagResult{false, err.Error()}
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return FlagResult{false, err.Error()}
}
fileSize := stat.Size()
var start, end int64
if options.Range == "" {
start = 0
end = int64(len(data)) - 1
if err := file.Truncate(0); err != nil {
return FlagResult{false, err.Error()}
}
} else {
start, end, err = ParseRange(options.Range, fileSize)
if err != nil {
return FlagResult{false, err.Error()}
}
writeLength := int64(len(data))
if writeLength != end-start+1 {
return FlagResult{false, "data length does not match range length"}
}
}
_, err = file.WriteAt(data, start)
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) ReadFile(path string, options IOOptions) FlagResult {
log.Printf("ReadFile [%s %s]: %s", options.Mode, options.Range, path)
fullPath := GetPath(path)
file, err := os.Open(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return FlagResult{false, err.Error()}
}
fileSize := stat.Size()
start, end, err := ParseRange(options.Range, fileSize)
if err != nil {
return FlagResult{false, err.Error()}
}
length := end - start + 1
buf := make([]byte, length)
n, err := file.ReadAt(buf, start)
if err != nil && err != io.EOF {
return FlagResult{false, err.Error()}
}
buf = buf[:n]
switch options.Mode {
case Text:
return FlagResult{true, string(buf)}
case Binary:
return FlagResult{true, base64.StdEncoding.EncodeToString(buf)}
default:
return FlagResult{false, "Unsupported IO mode: " + options.Mode}
}
}
func (a *App) MoveFile(source string, target string) FlagResult {
log.Printf("MoveFile: %s -> %s", source, target)
fullSource := GetPath(source)
fullTarget := GetPath(target)
if err := os.MkdirAll(filepath.Dir(fullTarget), os.ModePerm); err != nil {
return FlagResult{false, err.Error()}
}
if err := os.Rename(fullSource, fullTarget); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) RemoveFile(path string) FlagResult {
log.Printf("RemoveFile: %s", path)
fullPath := GetPath(path)
if err := os.RemoveAll(fullPath); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) CopyFile(src string, dst string) FlagResult {
log.Printf("CopyFile: %s -> %s", src, dst)
srcPath := GetPath(src)
dstPath := GetPath(dst)
srcFile, err := os.Open(srcPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer srcFile.Close()
if err := os.MkdirAll(filepath.Dir(dstPath), os.ModePerm); err != nil {
return FlagResult{false, err.Error()}
}
dstFile, err := os.Create(dstPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) MakeDir(path string) FlagResult {
log.Printf("MakeDir: %s", path)
fullPath := GetPath(path)
if err := os.MkdirAll(fullPath, os.ModePerm); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) ReadDir(path string) FlagResult {
log.Printf("ReadDir: %s", path)
fullPath := GetPath(path)
files, err := os.ReadDir(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
var result []string
for _, file := range files {
if info, err := file.Info(); err == nil {
result = append(result, fmt.Sprintf("%v,%v,%v", info.Name(), info.Size(), info.IsDir()))
}
}
return FlagResult{true, strings.Join(result, "|")}
}
func (a *App) OpenDir(path string) FlagResult {
log.Printf("OpenDir: %s", path)
fullPath := GetPath(path)
err := browser.OpenURL(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) OpenURI(uri string) FlagResult {
log.Printf("OpenURI: %s", uri)
err := browser.OpenURL(uri)
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) AbsolutePath(path string) FlagResult {
log.Printf("AbsolutePath: %s", path)
absPath := GetPath(path)
return FlagResult{true, absPath}
}
func (a *App) UnzipZIPFile(path string, output string) FlagResult {
log.Printf("UnzipZIPFile: %s -> %s", path, output)
fullPath := GetPath(path)
outputPath := GetPath(output)
archive, err := zip.OpenReader(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer archive.Close()
cleanOutputPath := outputPath + "/"
for _, f := range archive.File {
filePath := filepath.ToSlash(filepath.Clean(filepath.Join(outputPath, f.Name)))
if !strings.HasPrefix(filePath, cleanOutputPath) {
continue
}
if f.FileInfo().IsDir() {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
continue
}
fileInArchive, err := f.Open()
if err != nil {
continue
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
fileInArchive.Close()
continue
}
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
fileInArchive.Close()
dstFile.Close()
continue
}
fileInArchive.Close()
dstFile.Close()
}
return FlagResult{true, "Success"}
}
func (a *App) UnzipTarGZFile(path string, output string) FlagResult {
log.Printf("UnzipTarGZFile: %s -> %s", path, output)
fullPath := GetPath(path)
outputPath := GetPath(output)
gzipFile, err := os.Open(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return FlagResult{false, err.Error()}
}
defer gzipReader.Close()
tarReader := tar.NewReader(gzipReader)
cleanOutputPath := outputPath + "/"
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return FlagResult{false, err.Error()}
}
filePath := filepath.ToSlash(filepath.Clean(filepath.Join(outputPath, header.Name)))
if !strings.HasPrefix(filePath, cleanOutputPath) {
continue
}
if header.Typeflag == tar.TypeDir {
os.MkdirAll(filePath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
continue
}
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, header.FileInfo().Mode())
if err != nil {
continue
}
if _, err := io.Copy(dstFile, tarReader); err != nil {
dstFile.Close()
continue
}
dstFile.Close()
}
return FlagResult{true, "Success"}
}
func (a *App) UnzipGZFile(path string, output string) FlagResult {
log.Printf("UnzipGZFile: %s -> %s", path, output)
fullPath := GetPath(path)
outputPath := GetPath(output)
gzipFile, err := os.Open(fullPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer gzipFile.Close()
outputFile, err := os.Create(outputPath)
if err != nil {
return FlagResult{false, err.Error()}
}
defer outputFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
return FlagResult{false, err.Error()}
}
defer gzipReader.Close()
if _, err := io.Copy(outputFile, gzipReader); err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
func (a *App) FileExists(path string) FlagResult {
log.Printf("FileExists: %s", path)
path = GetPath(path)
_, err := os.Stat(path)
if err == nil {
return FlagResult{true, "true"}
}
if os.IsNotExist(err) {
return FlagResult{true, "false"}
}
return FlagResult{false, err.Error()}
}
================================================
FILE: bridge/mmdb.go
================================================
package bridge
import (
"encoding/json"
"errors"
"log"
"net"
"sync"
"github.com/oschwald/geoip2-golang"
)
type MMDBInstance = struct {
Refs map[string]bool
Reader *geoip2.Reader
}
var (
mu sync.RWMutex
mmdbMap = make(map[string]*MMDBInstance)
)
func (a *App) OpenMMDB(path string, id string) FlagResult {
log.Printf("OpenMMDB: %s -> %s", id, path)
mu.Lock()
defer mu.Unlock()
if db, exists := mmdbMap[path]; exists {
db.Refs[id] = true
return FlagResult{true, "Success"}
}
reader, err := geoip2.Open(GetPath(path))
if err != nil {
return FlagResult{false, "Failed to open mmdb: " + err.Error()}
}
mmdbMap[path] = &MMDBInstance{
Refs: map[string]bool{id: true},
Reader: reader,
}
return FlagResult{true, "Success"}
}
func (a *App) CloseMMDB(path string, id string) FlagResult {
log.Printf("CloseMMDB: %s -> %s", id, path)
mu.Lock()
defer mu.Unlock()
db, exists := mmdbMap[path]
if !exists {
return FlagResult{false, "Database not open: " + path}
}
if !db.Refs[id] {
return FlagResult{false, "Reference not found for: " + id}
}
delete(db.Refs, id)
if len(db.Refs) == 0 {
if err := db.Reader.Close(); err != nil {
return FlagResult{false, "Failed to close reader: " + err.Error()}
}
delete(mmdbMap, path)
}
return FlagResult{true, "Success"}
}
func (a *App) QueryMMDB(path string, ip string, dataType string) FlagResult {
log.Printf("QueryMMDB: %s -> %s", path, ip)
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return FlagResult{false, "Invalid IP address"}
}
mu.RLock()
db, exists := mmdbMap[path]
mu.RUnlock()
if !exists {
return FlagResult{false, "Database not open: " + path}
}
record, err := queryByType(db.Reader, parsedIP, dataType)
if err != nil {
return FlagResult{false, err.Error()}
}
bytes, err := json.Marshal(record)
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, string(bytes)}
}
func queryByType(reader *geoip2.Reader, ip net.IP, dataType string) (any, error) {
switch dataType {
case "ASN":
return reader.ASN(ip)
case "AnonymousIP":
return reader.AnonymousIP(ip)
case "City":
return reader.City(ip)
case "ConnectionType":
return reader.ConnectionType(ip)
case "Country":
return reader.Country(ip)
case "Domain":
return reader.Domain(ip)
case "Enterprise":
return reader.Enterprise(ip)
default:
return nil, errors.New("Unsupported query type: " + dataType)
}
}
================================================
FILE: bridge/net.go
================================================
package bridge
import (
"bytes"
"context"
"crypto/tls"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
func (a *App) Requests(method string, url string, headers map[string]string, body string, options RequestOptions) HTTPResult {
log.Printf("Requests: %v %v %v %v %v", method, url, headers, body, options)
client, ctx, cancel := withRequestOptionsClient(options)
req, err := http.NewRequestWithContext(ctx, method, url, strings.NewReader(body))
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
req.Header = GetHeader(headers)
if options.CancelId != "" {
runtime.EventsOn(a.Ctx, options.CancelId, func(data ...any) {
log.Printf("Requests Canceled: %v %v", method, url)
cancel()
})
defer runtime.EventsOff(a.Ctx, options.CancelId)
}
resp, err := client.Do(req)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
return HTTPResult{true, resp.StatusCode, resp.Header, string(b)}
}
func (a *App) Download(method string, url string, path string, headers map[string]string, event string, options RequestOptions) HTTPResult {
log.Printf("Download: %s %s %s %v %s %v", method, url, path, headers, event, options)
client, ctx, cancel := withRequestOptionsClient(options)
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
req.Header = GetHeader(headers)
if options.CancelId != "" {
runtime.EventsOn(a.Ctx, options.CancelId, func(data ...any) {
log.Printf("Download Canceled: %v %v", url, path)
cancel()
})
defer runtime.EventsOff(a.Ctx, options.CancelId)
}
resp, err := client.Do(req)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
defer resp.Body.Close()
path = GetPath(path)
err = os.MkdirAll(filepath.Dir(path), os.ModePerm)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
file, err := os.Create(path)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
defer file.Close()
reader := wrapWithProgress(resp.Body, resp.ContentLength, event, a)
_, err = io.Copy(file, reader)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
return HTTPResult{true, resp.StatusCode, resp.Header, "Success"}
}
func (a *App) Upload(method string, url string, path string, headers map[string]string, event string, options RequestOptions) HTTPResult {
log.Printf("Upload: %s %s %s %v %s %v", method, url, path, headers, event, options)
path = GetPath(path)
file, err := os.Open(path)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
defer file.Close()
fileStat, err := file.Stat()
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(options.FileField, path)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
reader := wrapWithProgress(file, fileStat.Size(), event, a)
_, err = io.Copy(part, reader)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
err = writer.Close()
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
client, ctx, cancel := withRequestOptionsClient(options)
if options.CancelId != "" {
runtime.EventsOn(a.Ctx, options.CancelId, func(data ...any) {
log.Printf("Upload Canceled: %v %v", url, path)
cancel()
})
defer runtime.EventsOff(a.Ctx, options.CancelId)
}
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
req.Header = GetHeader(headers)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := client.Do(req)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return HTTPResult{false, 500, nil, err.Error()}
}
return HTTPResult{true, resp.StatusCode, resp.Header, string(b)}
}
func (wt *WriteTracker) Write(p []byte) (n int, err error) {
n = len(p)
wt.Progress += int64(n)
shouldEmit := wt.Total <= 0 || wt.Progress-wt.LastEmitted >= wt.EmitThreshold || wt.Progress == wt.Total
if shouldEmit {
runtime.EventsEmit(wt.App.Ctx, wt.ProgressChange, wt.Progress, wt.Total)
wt.LastEmitted = wt.Progress
}
return n, nil
}
func wrapWithProgress(r io.Reader, size int64, event string, a *App) io.Reader {
if event == "" {
return r
}
return io.TeeReader(r, &WriteTracker{
Total: size,
EmitThreshold: 128 * 1024,
ProgressChange: event,
App: a,
})
}
func withRequestOptionsClient(options RequestOptions) (*http.Client, context.Context, context.CancelFunc) {
client := &http.Client{
Timeout: GetTimeout(options.Timeout),
Transport: &http.Transport{
Proxy: GetProxy(options.Proxy),
TLSClientConfig: &tls.Config{
InsecureSkipVerify: options.Insecure,
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if !options.Redirect {
return http.ErrUseLastResponse
}
return nil
},
}
ctx, cancel := context.WithCancel(context.Background())
return client, ctx, cancel
}
================================================
FILE: bridge/notification.go
================================================
package bridge
import (
"github.com/gen2brain/beeep"
)
func (a *App) Notify(title string, message string, icon string, options NotifyOptions) FlagResult {
fullPath := GetPath(icon)
beeep.AppName = options.AppName
var err error
if options.Beep {
err = beeep.Alert(title, message, fullPath)
} else {
err = beeep.Notify(title, message, fullPath)
}
if err != nil {
return FlagResult{false, err.Error()}
}
return FlagResult{true, "Success"}
}
================================================
FILE: bridge/server.go
================================================
package bridge
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"io"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
var requestCounter uint64
var serverMap sync.Map
type ResponseData struct {
Status int
Headers map[string]string
Body string
}
func (a *App) StartServer(address string, serverID string, options ServerOptions) FlagResult {
log.Printf("StartServer: %s %s %v", address, serverID, options)
mux := http.NewServeMux()
if options.StaticPath != "" && options.StaticRoute != "" {
static := GetPath(options.StaticPath)
fs := http.StripPrefix(options.StaticRoute, http.FileServer(http.Dir(static)))
mux.HandleFunc(options.StaticRoute, func(w http.ResponseWriter, r *http.Request) {
handleFileDownload(w, r, fs, options.StaticHeaders)
})
}
if options.UploadPath != "" && options.UploadRoute != "" {
uploadPath := GetPath(options.UploadPath)
if err := os.MkdirAll(uploadPath, os.ModePerm); err != nil {
return FlagResult{false, "Failed to create upload directory: " + err.Error()}
}
maxUploadSize := options.MaxUploadSize
if maxUploadSize <= 0 {
maxUploadSize = 50 * 1024 * 1024 // 50MB
}
mux.HandleFunc(options.UploadRoute, func(w http.ResponseWriter, r *http.Request) {
handleFileUpload(w, r, uploadPath, maxUploadSize, options.UploadHeaders)
})
}
var listener net.Listener
if options.Cert != "" && options.Key != "" {
cert, err := tls.LoadX509KeyPair(GetPath(options.Cert), GetPath(options.Key))
if err != nil {
return FlagResult{false, "Failed to load TLS cert: " + err.Error()}
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}
ln, err := net.Listen("tcp", address)
if err != nil {
return FlagResult{false, "Failed to bind address: " + err.Error()}
}
listener = tls.NewListener(ln, tlsConfig)
} else {
ln, err := net.Listen("tcp", address)
if err != nil {
return FlagResult{false, "Failed to bind address: " + err.Error()}
}
listener = ln
}
mux.HandleFunc("/", handleHttpRequest(a, serverID))
server := &http.Server{
Addr: address,
Handler: mux,
}
go func() {
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Printf("Server error on %s: %v", address, err)
}
}()
serverMap.Store(serverID, server)
return FlagResult{true, "Success"}
}
func (a *App) StopServer(id string) FlagResult {
log.Printf("StopServer: %s", id)
val, ok := serverMap.Load(id)
if !ok {
return FlagResult{false, "server not found"}
}
server, ok := val.(*http.Server)
if !ok {
return FlagResult{false, "invalid server type"}
}
err := server.Close()
if err != nil {
return FlagResult{false, err.Error()}
}
serverMap.Delete(id)
return FlagResult{true, "Success"}
}
func (a *App) ListServer() FlagResult {
log.Printf("ListServer")
var servers []string
serverMap.Range(func(key, value any) bool {
serverID, ok := key.(string)
if ok {
servers = append(servers, serverID)
}
return true
})
return FlagResult{true, strings.Join(servers, "|")}
}
func handleHttpRequest(a *App, serverID string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 20*1024*1024) // 20MB
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body: "+err.Error(), 500)
return
}
count := atomic.AddUint64(&requestCounter, 1)
requestID := serverID + strconv.FormatUint(count, 10)
respChan := make(chan ResponseData, 1)
ctx, cancel := context.WithTimeout(a.Ctx, 60*time.Second) // 60s
defer cancel()
runtime.EventsOn(ctx, requestID, func(data ...any) {
defer runtime.EventsOff(ctx, requestID)
resp := buildResponse(data)
respChan <- resp
})
runtime.EventsEmit(a.Ctx, serverID, requestID, r.Method, r.URL.RequestURI(), r.Header, body)
select {
case res := <-respChan:
for k, v := range res.Headers {
w.Header().Set(k, v)
}
w.WriteHeader(res.Status)
w.Write([]byte(res.Body))
case <-ctx.Done():
http.Error(w, "Request timed out", http.StatusGatewayTimeout)
}
}
}
func buildResponse(data []any) ResponseData {
resp := ResponseData{Status: 200, Headers: make(map[string]string), Body: "A sample http server"}
if len(data) >= 4 {
if status, ok := data[0].(float64); ok {
resp.Status = int(status)
}
if headers, ok := data[1].(string); ok {
json.Unmarshal([]byte(headers), &resp.Headers)
}
if body, ok := data[2].(string); ok {
resp.Body = body
}
if optionsStr, ok := data[3].(string); ok {
var ioOptions IOOptions
json.Unmarshal([]byte(optionsStr), &ioOptions)
if ioOptions.Mode == Binary {
decoded, err := base64.StdEncoding.DecodeString(resp.Body)
if err != nil {
resp.Status = 500
resp.Body = err.Error()
} else {
resp.Body = string(decoded)
}
}
}
}
return resp
}
func handleFileDownload(w http.ResponseWriter, r *http.Request, fs http.Handler, headers map[string]string) {
for key, value := range headers {
w.Header().Set(key, value)
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
fs.ServeHTTP(w, r)
}
func handleFileUpload(w http.ResponseWriter, r *http.Request, uploadPath string, maxUploadSize int64, headers map[string]string) {
for key, value := range headers {
w.Header().Set(key, value)
}
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
if r.Method != http.MethodPost && r.Method != http.MethodPut {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
contentType := r.Header.Get("Content-Type")
if strings.HasPrefix(contentType, "multipart/form-data") {
handleMultipartUpload(w, r, uploadPath)
} else {
handleRawUpload(w, r, uploadPath)
}
}
func handleMultipartUpload(w http.ResponseWriter, r *http.Request, uploadPath string) {
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, "Invalid multipart form: "+err.Error(), http.StatusBadRequest)
return
}
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if err != nil {
http.Error(w, "Error reading upload stream: "+err.Error(), http.StatusInternalServerError)
return
}
if part.FileName() == "" {
part.Close()
continue
}
dst, err := os.Create(filepath.Join(uploadPath, filepath.Base(part.FileName())))
if err != nil {
http.Error(w, "Error creating file: "+err.Error(), http.StatusInternalServerError)
return
}
if _, err = io.Copy(dst, part); err != nil {
dst.Close()
part.Close()
http.Error(w, "Error saving file: "+err.Error(), http.StatusInternalServerError)
return
}
dst.Close()
part.Close()
}
w.Write([]byte("File uploaded successfully"))
}
func handleRawUpload(w http.ResponseWriter, r *http.Request, uploadPath string) {
name := r.Header.Get("X-Filename")
if name == "" {
http.Error(w, "Missing X-Filename", 400)
return
}
dst, err := os.Create(filepath.Join(uploadPath, filepath.Base(name)))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer dst.Close()
if _, err := io.Copy(dst, r.Body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write([]byte("File uploaded successfully"))
}
================================================
FILE: bridge/tray.go
================================================
package bridge
import (
"log"
"os"
"github.com/energye/systray"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
func CreateTray(a *App, icon []byte) (trayStart, trayEnd func()) {
return systray.RunWithExternalLoop(func() {
systray.SetIcon(icon)
systray.SetTooltip("GUI.for.Cores")
systray.SetOnRClick(func(menu systray.IMenu) { menu.ShowMenu() })
systray.SetOnClick(func(menu systray.IMenu) {
if Env.OS == "darwin" {
menu.ShowMenu()
} else {
a.ShowMainWindow()
}
})
addClickMenuItem := func(title, tooltip string, action func()) {
m := systray.AddMenuItem(title, tooltip)
m.Click(action)
}
// Ensure the tray is still available if rolling-release fails
addClickMenuItem("Show", "Show", func() { a.ShowMainWindow() })
addClickMenuItem("Restart", "Restart", func() { a.RestartApp() })
addClickMenuItem("Exit", "Exit", func() { a.ExitApp() })
}, nil)
}
func (a *App) UpdateTray(tray TrayContent) {
log.Printf("UpdateTray")
updateTray(a, tray)
}
func (a *App) UpdateTrayMenus(menus []MenuItem) {
log.Printf("UpdateTrayMenus")
updateTrayMenus(a, menus)
}
func (a *App) UpdateTrayAndMenus(tray TrayContent, menus []MenuItem) {
log.Printf("UpdateTrayAndMenus")
updateTray(a, tray)
updateTrayMenus(a, menus)
}
func createMenuItem(menu MenuItem, a *App, parent *systray.MenuItem) {
if menu.Hidden {
return
}
switch menu.Type {
case "item":
var m *systray.MenuItem
if parent == nil {
m = systray.AddMenuItem(menu.Text, menu.Tooltip)
} else {
m = parent.AddSubMenuItem(menu.Text, menu.Tooltip)
}
m.Click(func() { go runtime.EventsEmit(a.Ctx, "onMenuItemClick", menu.Event) })
if menu.Checked {
m.Check()
}
for _, child := range menu.Children {
createMenuItem(child, a, m)
}
case "separator":
systray.AddSeparator()
}
}
func updateTray(a *App, tray TrayContent) {
if tray.Icon != "" {
ico, err := os.ReadFile(GetPath(tray.Icon))
if err == nil {
systray.SetIcon(ico)
}
}
if tray.Title != "" {
systray.SetTitle(tray.Title)
runtime.WindowSetTitle(a.Ctx, tray.Title)
}
if tray.Tooltip != "" {
systray.SetTooltip(tray.Tooltip)
}
}
func updateTrayMenus(a *App, menus []MenuItem) {
systray.ResetMenu()
for _, menu := range menus {
createMenuItem(menu, a, nil)
}
}
================================================
FILE: bridge/types.go
================================================
package bridge
import (
"context"
"net/http"
"github.com/wailsapp/wails/v2/pkg/menu"
)
// App struct
type App struct {
Ctx context.Context
AppMenu *menu.Menu
}
type EnvResult struct {
IsStartup bool `json:"-"`
PreventExit bool `json:"-"`
FromTaskSch bool `json:"-"`
WebviewPath string `json:"-"`
AppName string `json:"appName"`
AppVersion string `json:"appVersion"`
BasePath string `json:"basePath"`
OS string `json:"os"`
ARCH string `json:"arch"`
IsPrivileged bool `json:"isPrivileged"`
}
type RequestOptions struct {
Proxy string
Insecure bool
Redirect bool
Timeout int
CancelId string
FileField string
}
type ExecOptions struct {
PidFile string
StopOutputKeyword string
WorkingDirectory string
Convert bool
Env map[string]string
}
type Range struct {
Start *int64
End *int64
}
type IOOptions struct {
Mode string // Binary / Text
Range string // "start-end" / "start-" / "-end"
}
type FlagResult struct {
Flag bool `json:"flag"`
Data string `json:"data"`
}
type ServerOptions struct {
Cert string
Key string
StaticPath string
StaticRoute string
StaticHeaders map[string]string
UploadPath string
UploadRoute string
UploadHeaders map[string]string
MaxUploadSize int64
}
type NotifyOptions struct {
AppName string
Beep bool
}
type HTTPResult struct {
Flag bool `json:"flag"`
Status int `json:"status"`
Headers http.Header `json:"headers"`
Body string `json:"body"`
}
type AppConfig struct {
WindowStartState int `yaml:"windowStartState"`
WebviewGpuPolicy int `yaml:"webviewGpuPolicy"`
Width int `yaml:"width"`
Height int `yaml:"height"`
MultipleInstance bool `yaml:"multipleInstance"`
RollingRelease bool `yaml:"rollingRelease" default:"true"`
StartHidden bool
}
type TrayContent struct {
Icon string `json:"icon,omitempty"`
Title string `json:"title,omitempty"`
Tooltip string `json:"tooltip,omitempty"`
}
type WriteTracker struct {
Total int64
Progress int64
LastEmitted int64
EmitThreshold int64
ProgressChange string
App *App
}
type MenuItem struct {
Type string `json:"type"` // Menu Type: item / separator
Text string `json:"text"`
Tooltip string `json:"tooltip"`
Event string `json:"event"`
Children []MenuItem `json:"children"`
Hidden bool `json:"hidden"`
Checked bool `json:"checked"`
}
================================================
FILE: bridge/utils.go
================================================
package bridge
import (
"errors"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"golang.org/x/text/encoding/simplifiedchinese"
)
func GetPath(path string) string {
if !filepath.IsAbs(path) {
path = filepath.Join(Env.BasePath, path)
}
return filepath.ToSlash(filepath.Clean(path))
}
func GetProxy(_proxy string) func(*http.Request) (*url.URL, error) {
proxy := http.ProxyFromEnvironment
if _proxy != "" {
proxyUrl, err := url.Parse(_proxy)
if err == nil {
proxy = http.ProxyURL(proxyUrl)
}
}
return proxy
}
func GetTimeout(timeout int) time.Duration {
if timeout <= 0 {
return 15 * time.Second
}
return time.Duration(timeout) * time.Second
}
func GetHeader(headers map[string]string) http.Header {
header := make(http.Header, len(headers))
for key, value := range headers {
header.Set(key, value)
}
return header
}
func ConvertByte2String(byte []byte) string {
decodeBytes, _ := simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
return string(decodeBytes)
}
func ParseRange(s string, size int64) (start int64, end int64, err error) {
if s == "" {
return 0, size - 1, nil
}
s = strings.TrimSpace(s)
// "bytes=100-200"
s = strings.TrimPrefix(s, "bytes=")
parts := strings.SplitN(s, "-", 2)
if len(parts) != 2 {
return 0, 0, errors.New("invalid range format")
}
startStr := strings.TrimSpace(parts[0])
endStr := strings.TrimSpace(parts[1])
// "-200" last 200 bytes
if startStr == "" && endStr != "" {
e, err2 := strconv.ParseInt(endStr, 10, 64)
if err2 != nil || e < 0 {
return 0, 0, errors.New("invalid range value")
}
if e > size {
start = 0
} else {
start = size - e
}
end = size - 1
return start, end, nil
}
// "100-" from start to EOF
if startStr != "" && endStr == "" {
start, err = strconv.ParseInt(startStr, 10, 64)
if err != nil || start < 0 {
return 0, 0, errors.New("invalid range value")
}
end = size - 1
return start, end, nil
}
// "100-200"
if startStr != "" && endStr != "" {
start, err = strconv.ParseInt(startStr, 10, 64)
if err != nil || start < 0 {
return 0, 0, errors.New("invalid range value")
}
end, err = strconv.ParseInt(endStr, 10, 64)
if err != nil || end < 0 {
return 0, 0, errors.New("invalid range value")
}
if start > end {
return 0, 0, errors.New("invalid range: start > end")
}
if end >= size {
end = size - 1
}
return start, end, nil
}
return 0, 0, errors.New("invalid range format")
}
func RollingRelease(next http.Handler) http.Handler {
isDevVersion := strings.Contains(Env.AppVersion, "dev")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
url := r.URL.Path
isIndex := url == "/"
if isIndex {
w.Header().Set("Cache-Control", "no-cache")
} else {
w.Header().Set("Cache-Control", "max-age=31536000, immutable")
}
if isDevVersion || !Config.RollingRelease {
next.ServeHTTP(w, r)
return
}
if isIndex {
url = "/index.html"
}
filePath := GetPath("data/rolling-release" + url)
if _, err := os.Stat(filePath); err != nil {
next.ServeHTTP(w, r)
return
}
http.ServeFile(w, r, filePath)
})
}
================================================
FILE: build/README.md
================================================
# Build Directory
The build directory is used to house all the build files and assets for your application.
The structure is:
* bin - Output directory
* darwin - macOS specific files
* windows - Windows specific files
## Mac
The `darwin` directory holds files specific to Mac builds.
These may be customised and used as part of the build. To return these files to the default state, simply delete them
and
build with `wails build`.
The directory contains the following files:
- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`.
- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`.
## Windows
The `windows` directory contains the manifest and rc files used when building with `wails build`.
These may be customised for your application. To return these files to the default state, simply delete them and
build with `wails build`.
- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to
use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file
will be created using the `appicon.png` file in the build directory.
- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`.
- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer,
as well as the application itself (right click the exe -> properties -> details)
- `wails.exe.manifest` - The main application manifest file.
================================================
FILE: build/darwin/Info.dev.plist
================================================
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
================================================
FILE: build/darwin/Info.plist
================================================
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>{{.Info.ProductName}}</string>
<key>CFBundleExecutable</key>
<string>{{.Name}}</string>
<key>CFBundleIdentifier</key>
<string>com.wails.{{.Name}}</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>
<string>{{.Info.Comments}}</string>
<key>CFBundleShortVersionString</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleIconFile</key>
<string>iconfile</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>{{.Info.Copyright}}</string>
<key>LSUIElement</key>
<string>true</string>
{{if .Info.FileAssociations}}
<key>CFBundleDocumentTypes</key>
<array>
{{range .Info.FileAssociations}}
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>{{.Ext}}</string>
</array>
<key>CFBundleTypeName</key>
<string>{{.Name}}</string>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
<key>CFBundleTypeIconFile</key>
<string>{{.IconName}}</string>
</dict>
{{end}}
</array>
{{end}}
{{if .Info.Protocols}}
<key>CFBundleURLTypes</key>
<array>
{{range .Info.Protocols}}
<dict>
<key>CFBundleURLName</key>
<string>com.wails.{{.Scheme}}</string>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.Scheme}}</string>
</array>
<key>CFBundleTypeRole</key>
<string>{{.Role}}</string>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>
================================================
FILE: build/windows/info.json
================================================
{
"fixed": {
"file_version": "{{.Info.ProductVersion}}"
},
"info": {
"0000": {
"ProductVersion": "{{.Info.ProductVersion}}",
"CompanyName": "{{.Info.CompanyName}}",
"FileDescription": "{{.Info.ProductName}}",
"LegalCopyright": "{{.Info.Copyright}}",
"ProductName": "{{.Info.ProductName}}",
"Comments": "{{.Info.Comments}}"
}
}
}
================================================
FILE: build/windows/wails.exe.manifest
================================================
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
================================================
FILE: frontend/.editorconfig
================================================
[*.{ts,vue,less}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
================================================
FILE: frontend/.gitattributes
================================================
* text=auto eol=lf
================================================
FILE: frontend/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
package.json.md5
.eslintcache
================================================
FILE: frontend/.oxfmtrc.json
================================================
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "all",
"ignorePatterns": ["src/bridge/wailsjs/**"]
}
================================================
FILE: frontend/.oxlintrc.json
================================================
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "vue"],
"env": {
"browser": true
},
"categories": {
"correctness": "error"
},
"rules": {
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true,
"allowTernary": true
}
]
}
}
================================================
FILE: frontend/.vscode/extensions.json
================================================
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"oxc.oxc-vscode"
]
}
================================================
FILE: frontend/.vscode/settings.json
================================================
{
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"tsconfig.json": "tsconfig.*.json, env.d.ts",
"package.json": "package-lock.json, pnpm*, .oxlint*, eslint*, .oxfmtrc*, .editorconfig"
},
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "oxc.oxc-vscode"
}
================================================
FILE: frontend/env.d.ts
================================================
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_APP_VERSION: string
readonly VITE_APP_PROJECT_URL: string
readonly VITE_APP_LOCALES_URL: string
readonly VITE_APP_TG_GROUP: string
readonly VITE_APP_TG_CHANNEL: string
readonly VITE_APP_VERSION_API: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
================================================
FILE: frontend/eslint.config.js
================================================
import skipFormatting from 'eslint-config-prettier/flat'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import { globalIgnores } from 'eslint/config'
import pluginOxlint from 'eslint-plugin-oxlint'
import pluginVue from 'eslint-plugin-vue'
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{ts,vue}'],
},
globalIgnores(['**/dist/**', '**/wailsjs/**']),
...pluginVue.configs['flat/recommended'],
vueTsConfigs.recommended,
skipFormatting,
...pluginOxlint.buildFromOxlintConfigFile('.oxlintrc.json'),
{
rules: {
'@typescript-eslint/no-explicit-any': ['off'],
'vue/no-v-html': ['off'],
'vue/multi-word-component-names': [
'error',
{
ignores: ['index'],
},
],
},
},
)
================================================
FILE: frontend/index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_APP_TITLE%</title>
</head>
<body>
<div id="ipc-error" style="display: none" class="px-32 leading-relaxed">
<h1>WebView2 Runtime Error</h1>
<p>
Your system's WebView2 Runtime version is not compatible with this software.<br />
Please follow the steps below to fix the issue:
</p>
<ol>
<li>
Go to
<a href="https://developer.microsoft.com/en-us/Microsoft-edge/webview2" target="_blank">
<b> https://developer.microsoft.com/en-us/Microsoft-edge/webview2 </b>
</a>
and download the fixed version.
</li>
<li>Create a <b>data/WebView2</b> folder in the same directory as the software.</li>
<li>Place the downloaded <code>.cab</code> file into this folder.</li>
<li>Restart the application.</li>
</ol>
</div>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script>
if (!window.WailsInvoke) {
document.getElementById('ipc-error').style.display = 'block'
document.getElementById('app').style.display = 'none'
}
</script>
</body>
</html>
================================================
FILE: frontend/package.json
================================================
{
"name": "frontend",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "run-p type-check \"build-only {@}\" --",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "run-s lint:*",
"lint:oxlint": "oxlint . --fix",
"lint:eslint": "eslint . --fix --cache",
"format": "oxfmt src/"
},
"dependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.2",
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lang-json": "^6.0.2",
"@codemirror/lang-yaml": "^6.1.2",
"@codemirror/lint": "^6.9.5",
"@codemirror/merge": "^6.12.0",
"@codemirror/state": "^6.5.4",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.39.16",
"codemirror": "^6.0.2",
"croner": "10.0.1",
"marked": "^17.0.4",
"pinia": "^3.0.4",
"prettier": "^3.8.1",
"vue": "^3.5.29",
"vue-draggable-plus": "^0.6.1",
"vue-i18n": "^11.2.8",
"vue-router": "^5.0.3",
"yaml": "^2.8.2"
},
"devDependencies": {
"@tsconfig/node24": "^24.0.4",
"@types/node": "^25.3.5",
"@vitejs/plugin-vue": "^6.0.4",
"@vue/eslint-config-typescript": "^14.7.0",
"@vue/tsconfig": "^0.9.0",
"eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-oxlint": "^1.51.0",
"eslint-plugin-vue": "^10.8.0",
"less": "^4.5.1",
"npm-run-all2": "^8.0.4",
"oxfmt": "^0.36.0",
"oxlint": "^1.51.0",
"typescript": "~5.9.3",
"vite": "8.0.0-beta.16",
"vue-tsc": "^3.2.5"
}
}
================================================
FILE: frontend/src/App.vue
================================================
<script setup lang="ts">
import { ref } from 'vue'
import { EventsOn, WindowHide, IsStartup } from '@/bridge'
import { NavigationBar, TitleBar, SplashView, AboutView, CommandView } from '@/components'
import * as Stores from '@/stores'
import { exitApp, sampleID, sleep, message } from '@/utils'
const loading = ref(true)
const percent = ref(0)
const hasError = ref(false)
const envStore = Stores.useEnvStore()
const appStore = Stores.useAppStore()
const pluginsStore = Stores.usePluginsStore()
const profilesStore = Stores.useProfilesStore()
const rulesetsStore = Stores.useRulesetsStore()
const appSettings = Stores.useAppSettingsStore()
const kernelApiStore = Stores.useKernelApiStore()
const subscribesStore = Stores.useSubscribesStore()
const scheduledTasksStore = Stores.useScheduledTasksStore()
const handleRestartCore = async () => {
try {
await kernelApiStore.restartCore()
} catch (e: any) {
message.error(e.message || e)
}
}
EventsOn('onLaunchApp', async ([arg]: string[]) => {
if (!arg) return
const url = new URL(arg)
if (url.pathname.startsWith('//import-remote-profile')) {
const _url = url.searchParams.get('url')
const _name = decodeURIComponent(url.hash).slice(1) || sampleID()
if (!_url) {
message.error('URL missing')
return
}
try {
await subscribesStore.importSubscribe(_name, _url)
message.success('common.success')
} catch (e: any) {
message.error(e.message || e)
}
}
})
EventsOn('onBeforeExitApp', async () => {
if (appSettings.app.exitOnClose) {
exitApp()
} else {
WindowHide()
}
})
EventsOn('onExitApp', () => exitApp())
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const closeFn = appStore.modalStack.at(-1)
closeFn?.()
}
})
envStore.setupEnv().then(async () => {
const showError = (err: string) => {
hasError.value = true
message.error(err)
}
await Promise.all([
appSettings.setupAppSettings(),
profilesStore.setupProfiles(),
subscribesStore.setupSubscribes(),
rulesetsStore.setupRulesets(),
pluginsStore.setupPlugins(),
scheduledTasksStore.setupScheduledTasks(),
])
const startTime = performance.now()
percent.value = 20
if (await IsStartup()) {
await pluginsStore.onStartupTrigger().catch(showError)
}
percent.value = 40
await pluginsStore.onReadyTrigger().catch(showError)
const duration = performance.now() - startTime
percent.value = duration < 500 ? 80 : 100
await sleep(Math.max(0, 1000 - duration))
loading.value = false
kernelApiStore.initCoreState()
})
</script>
<template>
<SplashView v-if="loading">
<Progress
:percent="percent"
:status="hasError ? 'danger' : 'primary'"
:radius="10"
type="circle"
/>
</SplashView>
<template v-else>
<TitleBar />
<div class="flex-1 overflow-y-auto flex flex-col p-8">
<NavigationBar />
<div class="flex flex-col overflow-y-auto mt-8 px-8 h-full">
<RouterView #="{ Component }">
<KeepAlive>
<component :is="Component" />
</KeepAlive>
</RouterView>
</div>
</div>
</template>
<Modal
v-model:open="appStore.showAbout"
:cancel="false"
:submit="false"
mask-closable
min-width="50"
>
<AboutView />
</Modal>
<Menu
v-model="appStore.menuShow"
:position="appStore.menuPosition"
:menu-list="appStore.menuList"
/>
<Tips
v-model="appStore.tipsShow"
:position="appStore.tipsPosition"
:message="appStore.tipsMessage"
/>
<CommandView v-if="!loading" />
<div
v-if="kernelApiStore.needRestart || kernelApiStore.restarting"
class="fixed right-32 bottom-32"
>
<Button
v-tips="'home.overview.restart'"
:loading="kernelApiStore.restarting"
icon="restart"
class="rounded-full w-42 h-42 shadow"
@click="handleRestartCore"
/>
</div>
</template>
================================================
FILE: frontend/src/api/kernel.ts
================================================
import { Request } from '@/api/request'
import { WebSockets } from '@/api/websocket'
import { useProfilesStore } from '@/stores'
import type {
CoreApiConfig,
CoreApiProxies,
CoreApiConnections,
CoreApiWsDataMap,
} from '@/types/kernel'
type WsKey = keyof CoreApiWsDataMap
type WsChannel<K extends WsKey> = {
url: string
params?: Recordable
handlers: Array<(data: CoreApiWsDataMap[K]) => void>
isActive: boolean
connect?: () => void
disconnect?: () => void
}
export enum Api {
Configs = '/configs',
Memory = '/memory',
Proxies = '/proxies',
ProxyDelay = '/proxies/{0}/delay',
Connections = '/connections',
Traffic = '/traffic',
Logs = '/logs',
}
const setupCoreApi = (protocol: 'http' | 'ws') => {
const { currentProfile: profile } = useProfilesStore()
let base = `${protocol}://127.0.0.1:20123`
let bearer = ''
if (profile) {
const controller = profile.experimental.clash_api.external_controller || '127.0.0.1:20123'
const [, port = 20123] = controller.split(':')
base = `${protocol}://127.0.0.1:${port}`
bearer = profile.experimental.clash_api.secret
}
if (protocol === 'http') {
request.base = base
request.bearer = bearer
} else {
websocket.base = base
websocket.bearer = bearer
}
}
const request = new Request({ beforeRequest: () => setupCoreApi('http'), timeout: 60 * 1000 })
const websocket = new WebSockets({ beforeConnect: () => setupCoreApi('ws') })
const wsChannels: {
[K in WsKey]: WsChannel<K>
} = {
logs: { url: Api.Logs, isActive: false, handlers: [], params: { level: 'debug' } },
memory: { url: Api.Memory, isActive: false, handlers: [] },
traffic: { url: Api.Traffic, isActive: false, handlers: [] },
connections: { url: Api.Connections, isActive: false, handlers: [] },
}
const createCoreWSHandlerRegister = <K extends WsKey>(key: K) => {
const channel = wsChannels[key]
return (cb: (data: CoreApiWsDataMap[K]) => void) => {
channel.handlers.push(cb)
if (!channel.isActive && channel.connect) {
channel.connect()
channel.isActive = true
}
const unregister = () => {
const idx = channel.handlers.indexOf(cb)
idx !== -1 && channel.handlers.splice(idx, 1)
if (channel.isActive && channel.disconnect && channel.handlers.length === 0) {
channel.disconnect()
channel.isActive = false
}
}
return unregister
}
}
// restful api
export const getConfigs = () => request.get<CoreApiConfig>(Api.Configs)
export const setConfigs = (body = {}) => request.patch<null>(Api.Configs, body)
export const getProxies = () => request.get<CoreApiProxies>(Api.Proxies)
export const getConnections = () => request.get<CoreApiConnections>(Api.Connections)
export const deleteConnection = (id: string) => request.delete<null>(Api.Connections + '/' + id)
export const useProxy = (group: string, proxy: string) => {
return request.put<null>(Api.Proxies + '/' + group, { name: proxy })
}
export const getProxyDelay = (proxy: string, url: string, timeout: number) => {
return request.get<Record<string, number>>(Api.ProxyDelay.replace('{0}', proxy), {
url,
timeout,
})
}
// websocket api
export const onLogs = createCoreWSHandlerRegister('logs')
export const onMemory = createCoreWSHandlerRegister('memory')
export const onTraffic = createCoreWSHandlerRegister('traffic')
export const onConnections = createCoreWSHandlerRegister('connections')
export const initWebsocket = () => {
Object.values(wsChannels).forEach((channel) => {
const { connect, disconnect } = websocket.createWS({
url: channel.url,
params: channel.params,
cb: (data) => channel.handlers.forEach((cb) => cb(data)),
})
channel.connect = connect
channel.disconnect = disconnect
channel.isActive = false
if (channel.handlers.length > 0) {
channel.connect()
channel.isActive = true
}
})
}
export const destroyWebsocket = () => {
Object.values(wsChannels).forEach((channel) => {
channel.disconnect?.()
channel.connect = undefined
channel.disconnect = undefined
channel.isActive = false
})
}
================================================
FILE: frontend/src/api/request.ts
================================================
import { parse } from 'yaml'
type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
enum ResponseType {
JSON = 'JSON',
TEXT = 'TEXT',
YAML = 'YAML',
}
type RequestOptions = {
base?: string
bearer?: string
timeout?: number
responseType?: ResponseType
beforeRequest?: () => void
}
export class Request {
public base: string
public bearer: string
public timeout: number
public responseType: string
public beforeRequest: () => void
constructor(options: RequestOptions = {}) {
this.base = options.base || ''
this.bearer = options.bearer || ''
this.timeout = options.timeout || 10000
this.responseType = options.responseType || ResponseType.JSON
this.beforeRequest = options.beforeRequest || (() => 0)
}
private request = async <T>(
url: string,
options: { method: Method; body?: Record<string, any> },
) => {
this.beforeRequest()
const init: RequestInit = {
method: options.method,
signal: AbortSignal.timeout(this.timeout),
}
if (this.base) {
url = this.base + url
}
if (this.bearer) {
if (!init.headers) init.headers = {}
Object.assign(init.headers, { Authorization: `Bearer ${this.bearer}` })
}
if (['GET'].includes(options.method)) {
const query = new URLSearchParams(options.body || {}).toString()
query && (url += '?' + query)
}
if (['POST', 'PUT', 'PATCH'].includes(options.method)) {
init.body = JSON.stringify(options.body || {})
}
const res = await fetch(url, init)
if (res.status === 204) {
return null as T
}
if ([504, 401, 503].includes(res.status)) {
const { message } = await res.json()
throw message
}
if (this.responseType === ResponseType.TEXT) {
const text = await res.text()
return text as T
}
if (this.responseType === ResponseType.YAML) {
const text = await res.text()
return parse(text) as T
}
const json = await res.json()
return json as T
}
public get = <T>(url: string, body = {}) => this.request<T>(url, { method: 'GET', body })
public post = <T>(url: string, body = {}) => this.request<T>(url, { method: 'POST', body })
public put = <T>(url: string, body = {}) => this.request<T>(url, { method: 'PUT', body })
public patch = <T>(url: string, body = {}) => this.request<T>(url, { method: 'PATCH', body })
public delete = <T>(url: string) => this.request<T>(url, { method: 'DELETE' })
}
================================================
FILE: frontend/src/api/websocket.ts
================================================
type WebSocketsOptions = {
base?: string
bearer?: string
beforeConnect?: () => void
}
type Options = { url: string; cb: (data: any) => void; params?: Record<string, any> }
export class WebSockets {
public base: string
public bearer: string
public beforeConnect: () => void
constructor(options: WebSocketsOptions) {
this.base = options.base || ''
this.bearer = options.bearer || ''
this.beforeConnect = options.beforeConnect || (() => 0)
}
public createWS(options: Options) {
this.beforeConnect()
const params = { ...options.params, token: this.bearer }
const query = new URLSearchParams(params).toString()
const url = query ? `${options.url}?${query}` : options.url
let isManualClose = false
let ws: WebSocket | null = null
const connect = () => {
ws = new WebSocket(this.base + url)
ws.onmessage = (e) => options.cb(JSON.parse(e.data))
ws.onclose = () => {
setTimeout(() => {
if (!isManualClose) {
setTimeout(connect, 3000)
}
}, 1000)
}
}
const disconnect = () => {
isManualClose = true
if (ws) {
ws.onmessage = null
ws.onclose = null
ws.close()
ws = null
}
}
return { connect, disconnect }
}
}
================================================
FILE: frontend/src/assets/globalMethods.ts
================================================
import * as Vue from 'vue'
import { stringify, parse } from 'yaml'
import * as Bridge from '@/bridge'
import * as Stores from '@/stores'
import * as Utils from '@/utils'
/**
* Expose methods to be used by the plugin system
*/
window.Plugins = {
...Bridge,
...Utils,
...Stores,
YAML: {
parse,
stringify,
},
}
window.Vue = Vue
window.AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
================================================
FILE: frontend/src/assets/logo.ts
================================================
export default `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACKCAYAAABipUKtAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABMrSURBVHhe7Z0JVBRX1sddRmM0jhNHk3GS0Uy2yWYSRRZXBKEBge6mF8QVFMWNbgRUaDBBRY3mMxqXGLfBJRojJsYd950oSnAh4o57HLNMvpnvTJKJCe+7t+jWFm9DN/26qeque87veDynl+r7/99br169V9TzhkjKyWmqNWbG6wymfJ3RdElrMP0E/Ab//wH+/VJrzFoQm5IVptfrG5rfIoeHRH1dStYgrSHrHzpjFqsRg+mCNtmkMb9XDilHnNH0JFT7BlLomlkbMyL9CfNHySG1gErWQVv/hhDWEe7oUrL15o+UQwqhS81pCcKvIsSsNTBG+AQM1dr8FXKINbTJWREg1k1KRKfBMQR0FfNXySGmUI4b1xyEXwTn+wpSPI7AYHKN3A1EFCBGIFTnZUosV4FXFNqUCfKVQl2GLjX1Ua3R9B6c73+lRHIDFWC81X3T0lqZD0kOd0WMMdMXhD9DiOJ2wIS34d8Y86HJ4crQ63Mag/CTgF+qClHHVOCVh3p05h/NhyoH74g1ml6DJBcTyRcPBtPXsSkmtfmQ5XA2GGP1F6/bOmzKgpVwrjf9RCZdfODY4CO5GzgZCz8raLNy0+4tx766yC7f+oat2LCD9R87kUq4WLkFVwsq88+Rw5FYsq5As72w+JtLN++wa//4/h5ohsxZC6lkixUYG5hW4uyk+afJUV0sys9vsX73F3knzl+psBbemvJb37LVW/awgeMnUwkXJWCCW7rRmUrzz5SDik92HOhReKLs8pWvvyWFr0px2WU24f0lTJ+STSZdhFRoDaYVutRUuRtYx7Zt2x45UFw6vezKrbuU0NVRDmb5pGA/i8/MpRIuSvBehcaQFW3++d4dW/cfb19yrrzE3qq3RcnZcpYzL0863cBgwrHBsn4jMx83p8K7Ai7vGuwtOp0Gg7ofy8pvsauEqI6C3WDdjoNssGkqnXRxckNrzIwyp8U7Yteh4raHT5zdg+dwCyfPX2WXbn5DCusoJ85dYZM+WCatbmDIylOl5PzBnCLPjX3HTg8o+uriP63FtwbGAezqbVpYR8Bu8NmuQ2xI1jQ66aLEdCPWmNnbnCrPivW7iv5YePLs2uNnLpHCW4Pd4DKnboCflbtgBYuVzJUCrmcw/d2jusHe4tKwotILNymxq+MsdgNCVEfBAeb63YUsMfsdIuHiBAaI13F1kzmF0oxNxcVNC0+dmwdV/xslsD0I3eAWn25w6sJVNnXhShY7ZgKZdNEhjA0yl+ozMlqYUyqd2F9S6nu09MJZStTagGMDSlRHufL1d2zD3i/Y0AkS6gaGrGs6Q2a4ObXij91FJxKOnbn0CyWkM/DsBqcvXmNTF30kqW4A/y7ub8j5vTnN4gvcUqVLNqXHjsn+ad7q9cyeAV9t4NYNbn/HNu47At1gOp10EYLdQGvIVphTLp5QG7KfA5cesD7Y1Hfmsj1Fp0gRnYVvN7jO3lm8SjrdoPKewiJxdAPG6kPVD4eD+neVgxTA+/fLN+wgReQBXilQojoKdoNN+46yYW9JpxvA5eJVINSshPsjxjD+abhcKaAP7kEmzFnCDpWcIUV0Fp7zBqWXrrPpS1azPlK6UjBmLQw3GNzaDerrDNn9oQ19Tx6UDRJMU1j+9gOkiDwQZhEJUR3lKnSDzfuLWNJbM8jfIUZgbHAlNjk7xKyP6wJ3y8IXrqt6APaC8/PTYPR95NR5UkRncUU3kMrYAAoSn3XwIe6SMsvFNzTJJjU4zb499jWQ9PYMOOceIUXkAc9usOWAxLqB0VQO9DLL5nzgvDQ4azl8ONd9d1hZs1euE9b5USI6C99ucIPNWPqxpLqB1pi5wOlugKNMYSaK+BJeGKbMZjsKvyRF5AHXsYHEuoGwZzI5O9gsp/0xID29GXzAfOG8Qn0wZ/qmvc0Wrt3ssskjnt3gKxwbSKwbwL/z9aNyHjPLW33EJpu6whvOW3+Iuxj77gds77HTpIg84D02GCaxbqAxmILMMj8cSUmLGmkNmTPgfH+X/AA3MWDcJLZi405SQB5w7QaXb8CVgtTGBqYpOIFnlr0y9Pr8hlpj1kbqTXUFTh4ddNHkESJ0A6hkSlhHuN8NpDSLmD3eLH1lwGAvhX5h3YKTR2u37ycF5AF2g6q7j2pLZTeQyryB6T8P7F2EkX6dnPPtASePcCHHF6fOkSLyoOzKTa7dQAp3GM3PQqw8FVAvEBs44MLFHJSAPODdDYQ7jCJeixiZkJwM0j8qmCBm1DjyRWID2+t7y9eyo6UXSBF5wLMbbN5/VLRrEf0Vyqkg/p+BBvUU/YeRLxIrybmz2LZDxaSAPDjB+UphGnQDMe1TUA1PY+27Bs8F8Z8RDBAQEcOUSWnki8VKXNrbbMGajS6bSkaEXUscu4EY9inEjBzHsODbdwmad88A/mEq1rm3hkUOMZBvEjNp0+ez3UdPkgLyoHJswO8O4xQY0NZVN4gelsJC+w0VeMgAFoJjE5gmOZP8ALEyYNxElrd+u8umkhGes4gbYTDrzj2MmtEZLHzQyHviV2sApGuUnkUPHUN+mJjJmr2Y7S/+ihSQBzyvFLAbTF6wnOmJ38ETZVKq0PKtxa/RAJWoWUjfRJw+JD9YrCRkTmGrt+5hxwkBecHzSuHzPYXCMVO/xRm00MUjEkY/JLwFOwxQSTdlH2HUSH2JWMFz7KQPlrPDJ8pIAXnA854C7lOYOD+P/C21QTU8nSkGJJHCW7DbAEhAuFr4QJ1RWt0AZ+Q+3XmIFJAXlfcUaGEdAbsB7mgelFH7Zx9ht44cnEwKXhWHDGChh7ovU48cS365WMHJI1zN48qpZKEbcNzDiDfCqN9SHahL2MDhpNgUtTIAgnMGOKKkDkLMjJw4U1j7TwnIC+wGlKiOgt1gbcE+4dY49VseAKo+KtFIilwdtTaAhZ6aASxm1Hj6oEQKTh7N+egzl04l8+wGJefKWeZ7tp+LiPkPHziCFLgmnDYA0rm3lvWGcw51cGImZdocVnDYdesQEV67lrAbrNq8i/VLz3ngN+BleihxeWcvXAxgIVgfL0w2WB+g2MFta4vyN7MiF04ln4TzOa9uUFx2iWXMXCDkOSL+wUmd2sDVAEiXKB2LGppCJlvMjJ/5oUunkr88C93g6tekqI6CTztJnTyTFNRRuBvAQkjcEGESgkq2WBmUkStsYnXljaVTF64Jj7mlhLWHcugk2FFm/f1jUlBHcZkBkG7RscIUJJVssYKTR2/NXcr2Hy8lBeTBl2fL2bladIPz124L78XPmJ0nAQMgwuQRrjOQ2FQy3rL9eOtel95YOnURuoEdT0fFbet4z8D6vZIxgIXu6jimHpFOJlus4OTRlIUfuWxLO4IVjZVNCY9cvHEHLgOvPPQ+yRkAESaP4HqVSraYGZ7zLvt012GXdoPSi9eFwZ1FeLzlXFZ+k3wtIkkDWAjU9GdSWX9oASePZuatZYUnz5KC8AANho+4tQz0qNdYkLQBEFx51DthNJlsMWPIne2SqeR9MOhcvmEnW7xuG9ty4Bj5GmskbwALQfpBkpw8mrf6cy4PuMCq37j3KAi/lS3Kr2TJpwU1nm48xgBIl0idcCODSraYSZ8xnxUcrv2q5EMlZWzlpt33hLeAXaCmmUmPMoCFXn0GS2/yaPxkoXodubGE1b314HG2BISuKr5XGwDpGq1nymHSWoeIk0e4DtGeqeTCk+fYx1v3kcJb8GoDCISrhYOT2jrEwVlT2YqNu2xOJe8oLGFLPysgRbdGNoCZ7qo4YV0blWyxgpNHuA7Reir56OkLLH/7QVJsCtkAVuBUMi5xopItZvAJaPgMYnxMbt76HaTQtpANQNAjph9Tj5TW5NHgjFxS4JqQDWCDzhEaFpEwiky22IiGgSyu1Jm/egMpcnXYY4D3l60hBXUUSRnAQk/dQBYzWpzrECtX6oy6l2BXGWB2nhcbAOkSqRXdJlbl8LSHtl+5ygDvLlr5wPfUFskawAJuYq3rySP8/t6D6e1XrjKA6d155Pc5iuQNgAibWOto8kg9Ymy1269cYYBjZy6x2FFjye9zFI8wgAV3bmIVtl/BKYhKqjWuMMDS/E3kd9UGjzIA4o5NrPh0DXu3X/E2wPbDx5kqsWbj2YvHGQC5v4mVFrD2mIQl71QibcHTAFv2H2G6Eank99QWjzSABZ6bWDVw2Rk+yPHtV7wMsGjN5ywy4f7lJS882gCIsA4x3rlNrHh3knq6hj04a4ADxaUsNZfPJhAKjzeAhZ5axzexapIfnNSpDc4YYPXmnUyTNIb8XF54jQEQnErGSrZn3sDWM3UcxVEDLFy7hY2dNke4zufx/TXhVQawYM9updqc7ynmrfqcFJpi+uLV3K7v7UU2gA14GWDWsnxSbGsWrNnEknNmuKXiqyIbwAa8DDBx7lJSdAu5HyxnMVYPbnQ3sgFswMsAiRmTSOHx1DDMlMsUxHvciWwAG/AyALb17FkL7wsPg0Ic5EXZuHnkbqoYQPlfKmGehjsNYAHbvG5EOgur4bl97ua1gMD37xtAobxNJczTqAsDiJUXOwbkgvhtBQP4KVSHqIR5GrIB7vOnds+PBPHxD0bUr+cXppxJJczTkA1QSa8+Cf/XsGHDKBC/8g9H+YWpulMJ8zRkA1QSEBFzEGTvDjQTDABRH04DJVTSPAnZAEhixV9eeDkLNH8F+J2gPoZvqLKHn0L5K5U4T0E2wFDcfofVHwa0Ah78C6K+CpUBEvVb1cR5Ct5ugO7quAtNmjXrD1L/DWgkiF414FSghk5wlUqg1PFWA4T0HXIXOvzOJk2a9QWJXweaCmLbCp+kpEZwZTAO+JFKpFTxRgN0VfY53bLN0waQNQR4AUDxH2z9tsI3LPpVuEI4SiVTiniTAXrFDf7x1S498+ByTw1SYtW3BO4P+uwNHx8f6AZqE5wafqaSKiW8xQDdVX3OtGrT1gjy9QRwtq8JYF/V24qACFV7/zBlMZVYqeDpBgiJG/JT+y7BK6DqY0CyN4DHgYaoH5fw8YGxgUL5tr9CmjePPNkAMMI/98RT7caATEFAO8D5qrcVnXopO0A3OEElWcx4ogF6xQ35+fVuwaug6vHPwb8J8K16W/GKXt8YusFk6Aa/UMkWI55mgO7qfhee+Mtf00AOrHq8teu6qrcVviHKTnC5eJpKuNjwFAPAuf6XN3qErIGq14IEHQAc4bu+6m1FYGBgE3+FahpcMt6lEi8WPMEAPWL6Xn7ymWfHQdp7AVj1jwLurXpbEaBQ+4ERzlDJFwNSNgBWfYdARX7Dho11kOqOAN7Kdfy63tURGBjfxD9U+T9ivLEkVQP00PS/0ubZ5zMgvVj1fwXEU/W2orNC1QW6wTlKiLpCagYI6Zt4t0PPsPUNGzfWQ0p9AHFWva3wiYpqCpeLs+FKQRR3GKVkgEBN/+tPPfeSCdKIc/jPAvbP4YsthLUGYaqLlCjuRAoGgKr/tWNwxMZGjRr1gdR1AvC+vXSq3laEhoY2g24wry67gdgNEKgZcPPpF16egOkCngOkW/W2AgaHQSBGeVVx3IFYDRDSD6s+ckujJk3iIEW+QGtA+lVvK7oolc1hgPihu7uBGA3QUzvwVtuXXsuBtCiA5wHPq3pb4a+IDnXn6iMxGQDO9b91CoksgKrvB6nwAzy76m2FT4i+BZhgCQhUUVUw3ojFAEHagbfbvdx+Evx8XJyJq3RwebZ3VL2t8A9VR8Ap4QYlHC/q2gBY9b4h0TvMCzOx6p8A6MWZ3hjdIiMfh26wDMRySTeoSwP01A2688wrr0+BnxkOyFVfXQSEqqL9wlS3KBGdoS4MAFVfAabe3aRp84Hw0/yBJwG56muKAIWuJZhgFQjHrRu42wBBukHfPvtqh2nwc7DqcS3+Y0ADQA57wzdcGQNjAy7b191mAKz6cOW+ps2bD4KfEADIVe9MvBke3tovTPkJJaojuMMAQfr47557veMMOOwIQK56ngFVpQcj3KHEtQfXGiCxwj9cfaBp8xYJcKidgT8BjfG45eAY/sHRT4IJPqUErglXGQCq/p/Pv9FpJhxeb+AloDkgV70Lo75fWHRff4XqW0poW7jAAFD1MYebt2g5GI6pC9AGkKveXdExLKYNDBA3UGJT8DRAcGzCDy+86TsLDiMSeBmQq76OArqBaiCcFr6nRLeGlwECemu+eKxly0T4brnqxRIBkZFPwSlhCyW8BWcNEBQb/78vdvDDx6vh83XwCRty1Yss6sMpYbB/mPIH3gbo3Ft75LGWrbDquwL4dC2senkqV4zhExLVFoywnYcB4Fz/rxc7+s+Fj7VU/e8BueolEA38FdFJcFr41z0D2PEn5sIG3P+jUFD1RS0ebzUMPqsbIFe9FAOEf8ZPodqFBqjpr5Hjn3+trPr4f/+tU2d8lm40IFe99COnARggoXOk9lp1fz4mIn7UXf9w9V6o+qHwJqz6p4BHALnqPSHw+QahcUODtcasdTqj6Wez8BUxI8ed9wtXzflD69bx8LJgAOfw5ar35Ag3zHlEMyq7nXr0aNxtg2JjteN5vgUg37nzwsA274Wtvl69/wfOcO4gRpbr1QAAAABJRU5ErkJggg==`
================================================
FILE: frontend/src/assets/main.less
================================================
@import 'styles/variables.less';
@import 'styles/theme.less';
@import 'styles/reset.less';
@import 'styles/custom.less';
@import 'styles/utilities/index.less';
================================================
FILE: frontend/src/assets/polyfills.ts
================================================
// Polyfill for Promise.withResolvers()
if (typeof Promise.withResolvers !== 'function') {
Promise.withResolvers = function <T>() {
let resolve!: (value: T | PromiseLike<T>) => void
let reject!: (reason?: any) => void
const promise = new Promise<T>((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
}
================================================
FILE: frontend/src/assets/styles/custom.less
================================================
body[feature-outline='true'] {
* {
outline: 1px solid var(--color);
}
}
body[feature-no-animation='true'] {
* {
transition: none !important;
}
}
body[feature-no-rounded='true'] {
.rounded-2,
.rounded-4,
.rounded-6,
.rounded-8,
.rounded-16,
.rounded-32,
.rounded-9999,
.rounded-full {
border-radius: 0;
}
::-webkit-scrollbar-track {
border-radius: 0;
}
::-webkit-scrollbar-thumb {
border-radius: 0;
}
}
body[feature-border='true'] {
box-shadow: inset 0 0 1px var(--color);
}
body {
margin: 0;
color: var(--color);
background-color: var(--bg-color);
user-select: none;
-webkit-user-select: none;
line-height: 1.32;
}
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
.grid-list-grid {
flex: 1;
margin-top: 8px;
overflow-y: auto;
font-size: 12px;
.grid-list-item {
display: inline-block;
margin: 8px;
width: calc(100% / 3 - 16px);
}
}
.grid-list-list {
flex: 1;
margin-top: 8px;
overflow-y: auto;
font-size: 12px;
display: flex;
flex-direction: column;
gap: 16px;
padding: 8px;
}
.grid-list-header {
display: flex;
align-items: center;
padding: 0 8px;
}
.grid-list-empty {
height: 90%;
}
.form-item {
font-size: 14px;
padding: 4px 12px;
display: flex;
align-items: center;
justify-content: space-between;
}
.form-action {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
.rotation {
animation: rotate 1s infinite linear;
}
.hover\:\!bg-red {
&:hover {
background-color: rgba(255, 0, 0, 0.6) !important;
}
}
@keyframes clip {
from {
clip-path: circle(0% at var(--x) var(--y));
}
to {
clip-path: circle(100% at var(--x) var(--y));
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.slide-down-enter-active,
.slide-down-leave-active {
transition: transform 0.2s ease-out;
}
.slide-down-enter-from,
.slide-down-leave-to {
transform: translateY(-100%);
}
================================================
FILE: frontend/src/assets/styles/reset.less
================================================
div,
input {
box-sizing: border-box;
}
input {
font-family: inherit;
}
a {
text-decoration: none;
color: var(--color);
}
::view-transition-old(root) {
animation: none;
}
::view-transition-new(root) {
mix-blend-mode: normal;
animation: clip 0.5s ease-in;
}
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
border-radius: 6px;
background: var(--scrollbar-track-bg);
}
::-webkit-scrollbar-thumb {
border-radius: 6px;
background: var(--scrollbar-thumb-bg);
}
================================================
FILE: frontend/src/assets/styles/theme.less
================================================
:root {
body[theme-mode='light'] {
.set-theme(light);
}
body[theme-mode='dark'] {
.set-theme(dark);
}
}
.set-theme(@theme) {
--color: ~'var(--color-@{theme})';
--bg-color: ~'var(--bg-color-@{theme})';
// Scrollbar
--scrollbar-track-bg: ~'var(--scrollbar-track-bg-@{theme})';
--scrollbar-thumb-bg: ~'var(--scrollbar-thumb-bg-@{theme})';
// Button
--btn-normal-color: ~'var(--btn-normal-color-@{theme})';
--btn-normal-bg: ~'var(--btn-normal-bg-@{theme})';
--btn-normal-hover-color: ~'var(--btn-normal-hover-color-@{theme})';
--btn-normal-hover-border-color: ~'var(--btn-normal-hover-border-color-@{theme})';
--btn-normal-active-color: ~'var(--btn-normal-active-color-@{theme})';
--btn-normal-active-border-color: ~'var(--btn-normal-active-border-color-@{theme})';
--btn-primary-color: ~'var(--btn-primary-color-@{theme})';
--btn-primary-bg: ~'var(--btn-primary-bg-@{theme})';
--btn-primary-hover-bg: ~'var(--btn-primary-hover-bg-@{theme})';
--btn-primary-active-bg: ~'var(--btn-primary-active-bg-@{theme})';
--btn-link-color: ~'var(--btn-link-color-@{theme})';
--btn-link-bg: ~'var(--btn-link-bg-@{theme})';
--btn-link-hover-color: ~'var(--btn-link-hover-color-@{theme})';
--btn-link-active-color: ~'var(--btn-link-active-color-@{theme})';
--btn-text-color: ~'var(--btn-text-color-@{theme})';
--btn-text-bg: ~'var(--btn-text-bg-@{theme})';
--btn-text-hover-bg: ~'var(--btn-text-hover-bg-@{theme})';
--btn-text-active-bg: ~'var(--btn-text-active-bg-@{theme})';
// Radio
--radio-normal-color: ~'var(--radio-normal-color-@{theme})';
--radio-normal-bg: ~'var(--radio-normal-bg-@{theme})';
--radio-normal-hover-color: ~'var(--radio-normal-hover-color-@{theme})';
--radio-primary-color: ~'var(--radio-primary-color-@{theme})';
--radio-primary-bg: ~'var(--radio-primary-bg-@{theme})';
--radio-primary-hover-bg: ~'var(--radio-primary-hover-bg-@{theme})';
--radio-primary-active-bg: ~'var(--radio-primary-active-bg-@{theme})';
// Card
--card-color: ~'var(--card-color-@{theme})';
--card-bg: ~'var(--card-bg-@{theme})';
--card-hover-bg: ~'var(--card-hover-bg-@{theme})';
--card-active-bg: ~'var(--card-active-bg-@{theme})';
// Progress
--progress-bg: ~'var(--progress-bg-@{theme})';
--progress-inner-bg: ~'var(--progress-inner-bg-@{theme})';
// Dropdown
--dropdown-bg: ~'var(--dropdown-bg-@{theme})';
// Modal
--modal-bg: ~'var(--modal-bg-@{theme})';
--modal-mask-bg: ~'var(--modal-mask-bg-@{theme})';
// Switch
--switch-on-bg: ~'var(--switch-on-bg-@{theme})';
--switch-on-hover-bg: ~'var(--switch-on-hover-bg-@{theme})';
--switch-on-dot-bg: ~'var(--switch-on-dot-bg-@{theme})';
--switch-off-bg: ~'var(--switch-off-bg-@{theme})';
--switch-off-hover-bg: ~'var(--switch-off-hover-bg-@{theme})';
--switch-off-dot-bg: ~'var(--switch-off-dot-bg-@{theme})';
// Input
--input-color: ~'var(--input-color-@{theme})';
--input-bg: ~'var(--input-bg-@{theme})';
// ColorPicker
--color-picker-bg: ~'var(--color-picker-bg-@{theme})';
// Divider
--divider-color: ~'var(--divider-color-@{theme})';
// Select
--select-bg: ~'var(--select-bg-@{theme})';
// Toast
--toast-bg: ~'var(--toast-bg-@{theme})';
// Menu
--menu-bg: ~'var(--menu-bg-@{theme})';
// Table
--table-tr-odd-bg: ~'var(--table-tr-odd-bg-@{theme})';
--table-tr-even-bg: ~'var(--table-tr-even-bg-@{theme})';
--table-tr-odd-hover-bg: ~'var(--table-tr-odd-hover-bg-@{theme})';
--table-tr-even-hover-bg: ~'var(--table-tr-even-hover-bg-@{theme})';
}
================================================
FILE: frontend/src/assets/styles/utilities/display.less
================================================
.block {
display: block;
}
.inline-block {
display: inline-block;
}
.inline {
display: inline;
}
.flex {
display: flex;
}
.inline-flex {
display: inline-flex;
}
.grid {
display: grid;
}
.hidden {
display: none;
}
.\!hidden {
display: none !important;
}
.invisible {
visibility: hidden;
}
================================================
FILE: frontend/src/assets/styles/utilities/flex.less
================================================
.flex-row {
flex-direction: row;
}
.flex-row-reverse {
flex-direction: row-reverse;
}
.flex-col {
flex-direction: column;
}
.flex-col-reverse {
flex-direction: column-reverse;
}
.flex-wrap {
flex-wrap: wrap;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.items-end {
align-items: flex-end;
}
.items-stretch {
align-items: stretch;
}
.self-stretch {
align-self: stretch;
}
.justify-start {
justify-content: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-between {
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
}
.justify-evenly {
justify-content: space-evenly;
}
.shrink-0 {
flex-shrink: 0;
}
.flex-1 {
flex: 1 1 0%;
}
.flex-none {
flex: none;
}
.flex-auto {
flex: 1 1 auto;
}
================================================
FILE: frontend/src/assets/styles/utilities/gap.less
================================================
@gap-values: 0, 2, 4, 8, 10, 12, 16, 24, 32;
.generate-gaps(@i: 1) when (@i <= length(@gap-values)) {
@v: extract(@gap-values, @i);
.gap-@{v} {
gap: unit(@v, px);
}
.gap-x-@{v} {
column-gap: unit(@v, px);
}
.gap-y-@{v} {
row-gap: unit(@v, px);
}
.generate-gaps(@i + 1);
}
.generate-gaps();
================================================
FILE: frontend/src/assets/styles/utilities/grid.less
================================================
@cols: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 32, 64;
.generate-cols(@i: 1) when (@i <= length(@cols)) {
@n: extract(@cols, @i);
.grid-cols-@{n} {
grid-template-columns: repeat(@n, minmax(0, 1fr));
}
.col-span-@{n} {
grid-column: span @n / span @n;
}
.generate-cols(@i + 1);
}
.generate-cols();
================================================
FILE: frontend/src/assets/styles/utilities/index.less
================================================
@import 'spacing.less';
@import 'gap.less';
@import 'text.less';
@import 'display.less';
@import 'flex.less';
@import 'grid.less';
@import 'rounded.less';
@import 'size.less';
@import 'others.less';
================================================
FILE: frontend/src/assets/styles/utilities/others.less
================================================
.fixed {
position: fixed;
}
.sticky {
position: sticky;
}
.relative {
position: relative;
}
.absolute {
position: absolute;
}
.inset-0 {
inset: 0;
}
.z-2 {
z-index: 2;
}
.z-3 {
z-index: 3;
}
.z-9 {
z-index: 9;
}
.z-99 {
z-index: 99;
}
.z-999 {
z-index: 999;
}
.z-9999 {
z-index: 9999;
}
.top-0 {
top: 0;
}
.right-8 {
right: 8px;
}
.right-32 {
right: 32px;
}
.bottom-4 {
bottom: 4px;
}
.bottom-12 {
bottom: 12px;
}
.bottom-32 {
bottom: 32px;
}
.left-8 {
left: 8px;
}
.left-1\/2 {
left: 50%;
}
.backdrop-blur-sm {
backdrop-filter: blur(4px);
}
.blur-3xl {
filter: blur(64px);
}
.origin-center {
transform-origin: center;
}
.rotate-0 {
transform: rotate(0deg);
}
.-rotate-90 {
transform: rotate(-90deg);
}
.rotate-90 {
transform: rotate(90deg);
}
.rotate-180 {
transform: rotate(180deg);
}
.-translate-x-1\/2 {
transform: translateX(-50%);
}
.translate-y-0 {
transform: translateY(0);
}
.translate-y-full {
transform: translateY(100%);
}
.cursor-pointer {
cursor: pointer;
}
.cursor-move {
cursor: move;
}
.cursor-not-allowed {
cursor: not-allowed;
}
.pointer-events-none {
pointer-events: none;
}
.overflow-auto {
overflow: auto;
}
.overflow-y-auto {
overflow-y: auto;
}
.overflow-hidden {
overflow: hidden;
}
.transition-all {
transition-property: all;
}
.duration-100 {
transition-duration: 0.1s;
}
.duration-200 {
transition-duration: 0.2s;
}
.duration-400 {
transition-duration: 0.4s;
}
.shadow {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.outline-none {
outline: none;
}
.border-0 {
border-width: 0;
}
.bg-transparent {
background: transparent;
}
.border-collapse {
border-collapse: collapse;
}
================================================
FILE: frontend/src/assets/styles/utilities/rounded.less
================================================
@radius-values: 0, 2, 4, 6, 8, 16, 32, 9999;
.generate-rounded(@i: 1) when (@i <= length(@radius-values)) {
@r: extract(@radius-values, @i);
@name: ~'@{r}';
.rounded-@{name} {
border-radius: unit(@r, px);
}
.generate-rounded(@i + 1);
}
.generate-rounded();
.rounded-full {
border-radius: 9999px;
}
================================================
FILE: frontend/src/assets/styles/utilities/size.less
================================================
@size-values: 0, 8, 10, 12, 16, 18, 24, 26, 30, 32, 42, 64, 128, 256;
@percent-values: 25, 36, 50, 60, 75, 90, 100;
.generate-size(@i: 1) when (@i <= length(@size-values)) {
@v: extract(@size-values, @i);
.w-@{v} {
width: unit(@v, px);
}
.h-@{v} {
height: unit(@v, px);
}
.min-w-@{v} {
min-width: unit(@v, px);
}
.min-h-@{v} {
min-height: unit(@v, px);
}
.generate-size(@i + 1);
}
.generate-size();
.generate-percent(@i: 1) when (@i <= length(@percent-values)) {
@p: extract(@percent-values, @i);
.w-\[@{p}\%\] {
width: ~'@{p}%';
}
.h-\[@{p}\%\] {
height: ~'@{p}%';
}
.min-w-\[@{p}\%\] {
min-width: ~'@{p}%';
}
.min-h-\[@{p}\%\] {
min-height: ~'@{p}%';
}
.max-w-\[@{p}\%\] {
max-width: ~'@{p}%';
}
.max-h-\[@{p}\%\] {
max-height: ~'@{p}%';
}
.generate-percent(@i + 1);
}
.generate-percent();
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
================================================
FILE: frontend/src/assets/styles/utilities/spacing.less
================================================
@spacing-values: 0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 36;
@spacing-types: m, p;
@directions: '', t, r, b, l, x, y;
.generate-spacing(@typeIndex: 1) when (@typeIndex <= length(@spacing-types)) {
@type: extract(@spacing-types, @typeIndex);
.generate-direction(@dirIndex: 1) when (@dirIndex <= length(@directions)) {
@dir: extract(@directions, @dirIndex);
.generate-value(@valIndex: 1) when (@valIndex <= length(@spacing-values)) {
@val: extract(@spacing-values, @valIndex);
@class: ~'@{type}@{dir}-@{val}';
.@{class} {
.apply-spacing(@type, @dir, unit(@val, px));
}
.generate-value(@valIndex + 1);
}
.generate-auto() when (@type = m) {
@class: ~'@{type}@{dir}-auto';
.@{class} {
.apply-spacing(@type, @dir, auto);
}
}
.generate-value();
.generate-auto();
.generate-direction(@dirIndex + 1);
}
.generate-direction();
.generate-spacing(@typeIndex + 1);
}
.generate-spacing();
.apply-spacing(@type, '', @value) when (@type = m) {
margin: @value;
}
.apply-spacing(@type, '', @value) when (@type = p) {
padding: @value;
}
.apply-spacing(@type, t, @value) when (@type = m) {
margin-top: @value;
}
.apply-spacing(@type, t, @value) when (@type = p) {
padding-top: @value;
}
.apply-spacing(@type, r, @value) when (@type = m) {
margin-right: @value;
}
.apply-spacing(@type, r, @value) when (@type = p) {
padding-right: @value;
}
.apply-spacing(@type, b, @value) when (@type = m) {
margin-bottom: @value;
}
.apply-spacing(@type, b, @value) when (@type = p) {
padding-bottom: @value;
}
.apply-spacing(@type, l, @value) when (@type = m) {
margin-left: @value;
}
.apply-spacing(@type, l, @value) when (@type = p) {
padding-left: @value;
}
.apply-spacing(@type, x, @value) when (@type = m) {
margin-left: @value;
margin-right: @value;
}
.apply-spacing(@type, x, @value) when (@type = p) {
padding-left: @value;
padding-right: @value;
}
.apply-spacing(@type, y, @value) when (@type = m) {
margin-top: @value;
margin-bottom: @value;
}
.apply-spacing(@type, y, @value) when (@type = p) {
padding-top: @value;
padding-bottom: @value;
}
================================================
FILE: frontend/src/assets/styles/utilities/text.less
================================================
@text-sizes: 10, 12, 14, 16, 18, 20, 24, 32;
.generate-text(@i: 1) when (@i <= length(@text-sizes)) {
@s: extract(@text-sizes, @i);
.text-@{s} {
font-size: unit(@s, px);
}
.generate-text(@i + 1);
}
.generate-text();
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.align-middle {
vertical-align: middle;
}
.line-clamp-1 {
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.underline {
text-decoration: underline;
}
.text-nowrap {
text-wrap: nowrap;
}
.text-ellipsis {
text-overflow: ellipsis;
}
.break-all {
word-break: break-all;
}
.break-keep {
word-break: keep-all;
}
.whitespace-nowrap {
white-space: nowrap;
}
.whitespace-pre-wrap {
white-space: pre-wrap;
}
.leading-relaxed {
line-height: 1.625;
}
.italic {
font-style: italic;
}
.font-bold {
font-weight: bold;
}
.font-normal {
font-weight: normal;
}
.select-text {
user-select: text;
-webkit-user-select: text;
}
================================================
FILE: frontend/src/assets/styles/variables.less
================================================
:root {
--x: 0px;
--y: 0px;
--primary-color: rgb(0, 89, 214);
--secondary-color: rgb(5, 62, 142);
// Color & BackgroundColor
--color-light: #000;
--bg-color-light: rgba(246, 246, 246, 0.85);
--color-dark: #fff;
--bg-color-dark: rgba(0, 0, 0, 0.85);
// Scrollbar
--scrollbar-track-bg-light: rgb(220, 220, 220);
--scrollbar-thumb-bg-light: var(--primary-color);
--scrollbar-track-bg-dark: rgb(73, 73, 73);
--scrollbar-thumb-bg-dark: var(--primary-color);
// Button
--btn-normal-color-light: #000000;
--btn-normal-bg-light: rgb(255, 255, 255);
--btn-normal-hover-color-light: var(--primary-color);
--btn-normal-hover-border-color-light: var(--primary-color);
--btn-normal-active-color-light: var(--secondary-color);
--btn-normal-active-border-color-light: var(--secondary-color);
--btn-normal-color-dark: #000000;
--btn-normal-bg-dark: rgb(255, 255, 255);
--btn-normal-hover-color-dark: var(--primary-color);
--btn-normal-hover-border-color-dark: var(--primary-color);
--btn-normal-active-color-dark: var(--secondary-color);
--btn-normal-active-border-color-dark: var(--secondary-color);
--btn-primary-color-light: rgb(255, 255, 255);
--btn-primary-bg-light: var(--primary-color);
--btn-primary-hover-bg-light: var(--secondary-color);
--btn-primary-active-bg-light: var(--primary-color);
--btn-primary-color-dark: rgb(255, 255, 255);
--btn-primary-bg-dark: var(--primary-color);
--btn-primary-hover-bg-dark: var(--secondary-color);
--btn-primary-active-bg-dark: var(--primary-color);
--btn-link-color-light: var(--primary-color);
--btn-link-bg-light: transparent;
--btn-link-hover-color-light: var(--secondary-color);
--btn-link-active-color-light: var(--primary-color);
--btn-link-color-dark: var(--primary-color);
--btn-link-bg-dark: transparent;
--btn-link-hover-color-dark: var(--secondary-color);
--btn-link-active-color-dark: var(--primary-color);
--btn-text-color-light: rgb(46, 46, 46);
--btn-text-bg-light: transparent;
--btn-text-hover-bg-light: rgb(232, 232, 232);
--btn-text-active-bg-light: rgb(206, 206, 206);
--btn-text-color-dark: rgb(230, 230, 230);
--btn-text-bg-dark: transparent;
--btn-text-hoer-color-dark: #222222;
--btn-text-hover-bg-dark: rgba(255, 255, 255, 0.2);
--btn-text-active-color-dark: #161616;
--btn-text-active-bg-dark: rgba(255, 255, 255, 0.4);
// Radio
--radio-normal-color-light: #000;
--radio-normal-bg-light: rgba(255, 255, 255, 1);
--radio-normal-hover-color-light: var(--primary-color);
--radio-primary-color-light: #fff;
--radio-primary-bg-light: var(--primary-color);
--radio-primary-hover-bg-light: var(--secondary-color);
--radio-primary-active-bg-light: var(--primary-color);
--radio-normal-color-dark: #ededed;
--radio-normal-bg-dark: rgba(255, 255, 255, 0.06);
--radio-normal-hover-color-dark: var(--primary-color);
--radio-primary-color-dark: #fff;
--radio-primary-bg-dark: var(--primary-color);
--radio-primary-hover-bg-dark: var(--secondary-color);
--radio-primary-active-bg-dark: var(--primary-color);
// Card
--card-color-light: rgb(95, 95, 95);
--card-bg-light: rgba(255, 255, 255, 0.6);
--card-hover-bg-light: rgba(255, 255, 255, 0.6);
--card-active-bg-light: rgba(255, 255, 255, 0.4);
--card-color-dark: rgb(255, 255, 255);
--card-bg-dark: rgba(255, 255, 255, 0.06);
--card-hover-bg-dark: rgba(255, 255, 255, 0.1);
--card-active-bg-dark: rgba(255, 255, 255, 0.04);
// Progress
--progress-bg-light: rgba(0, 0, 0, 0.08);
--progress-inner-bg-light: var(--primary-color);
--progress-bg-dark: rgba(221, 221, 221, 0.08);
--progress-inner-bg-dark: var(--primary-color);
// Dropdown
--dropdown-bg-light: rgba(255, 255, 255, 0.8);
--dropdown-bg-dark: rgba(62, 62, 62, 0.8);
// Modal
--modal-bg-light: #f6f6f6;
--modal-mask-bg-light: rgba(255, 255, 255, 0.4);
--modal-bg-dark: #343434;
--modal-mask-bg-dark: rgba(0, 0, 0, 0.4);
// Switch
--switch-on-bg-light: var(--primary-color);
--switch-on-hover-bg-light: var(--secondary-color);
--switch-on-dot-bg-light: #fff;
--switch-on-bg-dark: var(--primary-color);
--switch-on-hover-bg-dark: var(--secondary-color);
--switch-on-dot-bg-dark: #fff;
--switch-off-bg-light: rgba(0, 0, 0, 0.06);
--switch-off-hover-bg-light: rgba(0, 0, 0, 0.1);
--switch-off-dot-bg-light: #fff;
--switch-off-bg-dark: rgba(255, 255, 255, 0.1);
--switch-off-hover-bg-dark: rgba(255, 255, 255, 0.06);
--switch-off-dot-bg-dark: #fff;
// Input
--input-color-light: #000;
--input-bg-light: rgba(255, 255, 255, 1);
--input-color-dark: #fff;
--input-bg-dark: rgba(255, 255, 255, 0.06);
// ColorPicker
--color-picker-bg-light: #fff;
--color-picker-bg-dark: rgba(255, 255, 255, 0.06);
// Divider
--divider-color-light: #c6c6c6;
--divider-color-dark: #4d4d4d;
// Select
--select-bg-light: rgba(255, 255, 255, 1);
--select-bg-dark: rgba(255, 255, 255, 0.06);
// Toast
--toast-bg-light: #fff;
--toast-bg-dark: #343434;
// Menu
--menu-bg-light: rgba(255, 255, 255, 0.8);
--menu-bg-dark: rgba(62, 62, 62, 0.8);
// Table
--table-tr-odd-bg-light: rgb(247, 247, 247);
--table-tr-even-bg-light: rgb(238, 238, 238);
--table-tr-odd-hover-bg-light: rgb(202, 202, 202);
--table-tr-even-hover-bg-light: rgb(202, 202, 202);
--table-tr-odd-bg-dark: #2e2e2e;
--table-tr-even-bg-dark: rgb(37, 37, 37);
--table-tr-odd-hover-bg-dark: rgb(61, 61, 61);
--table-tr-even-hover-bg-dark: rgb(61, 61, 61);
// Delay color
--level-0-color: #808080;
--level-1-color: #29b280;
--level-2-color: #b68b1f;
--level-3-color: #ea6060;
--level-4-color: #f00e0e;
}
================================================
FILE: frontend/src/bridge/app.ts
================================================
import * as App from '@wails/go/bridge/App'
export const RestartApp = App.RestartApp
export const ExitApp = App.ExitApp
export const ShowMainWindow = App.ShowMainWindow
export const UpdateTray = App.UpdateTray
export const UpdateTrayMenus = App.UpdateTrayMenus
export const UpdateTrayAndMenus = App.UpdateTrayAndMenus
export const GetEnv = App.GetEnv
export const IsStartup = App.IsStartup
export const GetInterfaces = async () => {
const { flag, data } = await App.GetInterfaces()
if (!flag) {
throw data
}
return data.split('|')
}
================================================
FILE: frontend/src/bridge/exec.ts
================================================
import * as App from '@wails/go/bridge/App'
import { EventsOn, EventsOff } from '@wails/runtime/runtime'
import { sampleID } from '@/utils'
interface ExecOptions {
PidFile?: string
Convert?: boolean
Env?: Record<string, any>
StopOutputKeyword?: string
WorkingDirectory?: string
convert?: boolean
env?: Record<string, any>
stopOutputKeyword?: string
}
const mergeExecOptions = (options: ExecOptions) => {
const mergedExecOpts = {
PidFile: options.PidFile ?? '',
Convert: options.Convert ?? options.convert ?? false,
Env: options.Env ?? options.env ?? {},
StopOutputKeyword: options.StopOutputKeyword ?? options.stopOutputKeyword ?? '',
WorkingDirectory: options.WorkingDirectory ?? '',
}
return mergedExecOpts
}
export const Exec = async (path: string, args: string[], options: ExecOptions = {}) => {
const { flag, data } = await App.Exec(path, args, mergeExecOptions(options))
if (!flag) {
throw data
}
return data
}
export const ExecBackground = async (
path: string,
args: string[] = [],
onOut?: (out: string) => void,
onEnd?: () => void,
options: ExecOptions = {},
) => {
const outEvent = (onOut && sampleID()) || ''
const endEvent = (onEnd && sampleID()) || (outEvent && sampleID()) || ''
const { flag, data } = await App.ExecBackground(
path,
args,
outEvent,
endEvent,
mergeExecOptions(options),
)
if (!flag) {
throw data
}
if (outEvent) {
EventsOn(outEvent, onOut!)
}
if (endEvent) {
EventsOn(endEvent, () => {
outEvent && EventsOff(outEvent)
EventsOff(endEvent)
onEnd?.()
})
}
return Number(data)
}
export const ProcessInfo = async (pid: number) => {
const { flag, data } = await App.ProcessInfo(pid)
if (!flag) {
throw data
}
return data
}
export const ProcessMemory = async (pid: number) => {
const { flag, data } = await App.ProcessMemory(pid)
if (!flag) {
throw data
}
return Number(data)
}
export const KillProcess = async (pid: number, timeout = 10) => {
const { flag, data } = await App.KillProcess(pid, timeout)
if (!flag) {
throw data
}
return data
}
================================================
FILE: frontend/src/bridge/index.ts
================================================
export * from '@wails/runtime/runtime'
export * from './io'
export * from './net'
export * from './exec'
export * from './app'
export * from './server'
export * from './mmdb'
export * from './notification'
================================================
FILE: frontend/src/bridge/io.ts
================================================
import * as App from '@wails/go/bridge/App'
interface IOOptions {
Mode?: 'Binary' | 'Text'
Range?: string
}
export const WriteFile = async (path: string, content: string, options: IOOptions = {}) => {
const { flag, data } = await App.WriteFile(path, content, { Mode: 'Text', Range: '', ...options })
if (!flag) {
throw data
}
return data
}
export const ReadFile = async (path: string, options: IOOptions = {}) => {
const { flag, data } = await App.ReadFile(path, { Mode: 'Text', Range: '', ...options })
if (!flag) {
throw data
}
return data
}
export const MoveFile = async (source: string, target: string) => {
const { flag, data } = await App.MoveFile(source, target)
if (!flag) {
throw data
}
return data
}
export const RemoveFile = async (path: string) => {
const { flag, data } = await App.RemoveFile(path)
if (!flag) {
throw data
}
return data
}
export const CopyFile = async (source: string, target: string) => {
const { flag, data } = await App.CopyFile(source, target)
if (!flag) {
throw data
}
return data
}
export const FileExists = async (path: string) => {
const { flag, data } = await App.FileExists(path)
if (!flag) {
throw data
}
return data === 'true'
}
export const AbsolutePath = async (path: string) => {
const { flag, data } = await App.AbsolutePath(path)
if (!flag) {
throw data
}
return data
}
export const MakeDir = async (path: string) => {
const { flag, data } = await App.MakeDir(path)
if (!flag) {
throw data
}
return data
}
export const ReadDir = async (path: string) => {
const { flag, data } = await App.ReadDir(path)
if (!flag) {
throw data
}
return data
.split('|')
.filter((v) => v)
.map((v) => {
const [name, size, isDir] = v.split(',') as [string, string, string]
return { name, size: Number(size), isDir: isDir === 'true' }
})
}
export const OpenDir = async (path: string) => {
const { flag, data } = await App.OpenDir(path)
if (!flag) {
throw data
}
return data
}
export const OpenURI = async (uri: string) => {
const { flag, data } = await App.OpenURI(uri)
if (!flag) {
throw data
}
return data
}
export const UnzipZIPFile = async (path: string, output: string) => {
const { flag, data } = await App.UnzipZIPFile(path, output)
if (!flag) {
throw data
}
return data
}
export const UnzipGZFile = async (path: string, output: string) => {
const { flag, data } = await App.UnzipGZFile(path, output)
if (!flag) {
throw data
}
return data
}
export const UnzipTarGZFile = async (path: string, output: string) => {
const { flag, data } = await App.UnzipTarGZFile(path, output)
if (!flag) {
throw data
}
return data
}
================================================
FILE: frontend/src/bridge/mmdb.ts
================================================
import * as App from '@wails/go/bridge/App'
type QueryType =
| 'ASN'
| 'AnonymousIP'
| 'City'
| 'ConnectionType'
| 'Country'
| 'Domain'
| 'Enterprise'
export const OpenMMDB = async (path: string, id: string) => {
const { flag, data } = await App.OpenMMDB(path, id)
if (!flag) {
throw data
}
return {
close: () => CloseMMDB(path, id),
query: (ip: string, type: QueryType) => QueryMMDB(path, ip, type),
}
}
export const CloseMMDB = async (path: string, id: string) => {
const { flag, data } = await App.CloseMMDB(path, id)
if (!flag) {
throw data
}
return data
}
export const QueryMMDB = async (path: string, ip: string, type: QueryType = 'Country') => {
const { flag, data } = await App.QueryMMDB(path, ip, type)
if (!flag) {
throw data
}
return JSON.parse(data)
}
================================================
FILE: frontend/src/bridge/net.ts
================================================
import * as App from '@wails/go/bridge/App'
import { EventsOn, EventsOff, EventsEmit } from '@wails/runtime/runtime'
import { RequestMethod } from '@/enums/app'
import { sampleID, getUserAgent } from '@/utils'
import { GetSystemOrKernelProxy } from '@/utils/helper'
interface Request {
method: RequestMethod
url: string
headers?: {
'Content-Type'?: 'application/json' | 'application/x-www-form-urlencoded' | 'text/plain'
} & Record<string, string>
body?: any
options?: {
Proxy?: string
Insecure?: boolean
Redirect?: boolean
Timeout?: number
CancelId?: string
FileField?: string
}
}
interface Response<T = any> {
status: number
headers: Record<string, string | string[]>
body: T
}
const mergeRequestOptions = async (options: Request['options']) => {
const mergedReqOpts: Required<Request['options']> = {
Proxy: await GetSystemOrKernelProxy(),
Insecure: false,
Redirect: true,
Timeout: 15, // 15 seconds
CancelId: '',
FileField: 'file',
...options,
}
return mergedReqOpts
}
const transformResponseHeaders = (headers: Record<string, string[]>): Response['headers'] => {
return Object.fromEntries(
Object.entries(headers).map(([key, value]) => [key, value.length > 1 ? value : value[0]!]),
)
}
const transformResponseBody = <T>(body: Response['body'], headers: Response['headers']) => {
if (headers['Content-Type']?.includes('application/json')) {
try {
body = JSON.parse(body)
} catch {
console.warn('Failed to parse response body as JSON:', body)
}
}
return body as T
}
const transformRequest = async (
headers: Request['headers'],
body: Request['body'],
options: Request['options'],
) => {
const transformedHeaders = { 'User-Agent': getUserAgent(), ...headers }
if (transformedHeaders['Content-Type']?.includes('application/json')) {
body && (body = JSON.stringify(body))
} else if (transformedHeaders['Content-Type']?.includes('application/x-www-form-urlencoded')) {
body && (body = new URLSearchParams(body).toString())
}
const transformedReqOpts = await mergeRequestOptions(options)
return [transformedHeaders, body, transformedReqOpts] as const
}
const transformResponse = <T = any>(
status: Response['status'],
headers: Record<string, string[]>,
body: Response['body'],
) => {
const transformedHeaders = transformResponseHeaders(headers)
const transformedBody = transformResponseBody<T>(body, transformedHeaders)
return { status, headers: transformedHeaders, body: transformedBody }
}
interface RequestWithProgressOptions {
Method?: Request['method']
}
const requestWithProgress = (fnName: 'Download' | 'Upload') => {
return async (
url: Request['url'],
path: string,
headers: Request['headers'] = {},
progress?: (progress: number, total: number) => void,
options: Request['options'] & RequestWithProgressOptions = {},
) => {
const [_headers, , _options] = await transformRequest(headers, null, {
Timeout: 20 * 60, // 20 minutes
...options,
})
const method =
options.Method ?? { Download: RequestMethod.Get, Upload: RequestMethod.Post }[fnName]
const progressEvent = (progress && sampleID()) || ''
if (progressEvent) {
EventsOn(progressEvent, progress!)
}
const {
flag,
status,
headers: respHeaders,
body: respBody,
} = await App[fnName](method, url, path, _headers, progressEvent, _options)
if (progressEvent) {
EventsOff(progressEvent)
}
if (!flag) throw respBody
return transformResponse(status, respHeaders, respBody)
}
}
const requestWithBody = (method: RequestMethod.Put | RequestMethod.Post | RequestMethod.Patch) => {
return async <T = any>(
url: string,
headers: Request['headers'] = {},
body = {},
options = {},
) => {
const [_headers, _body, _options] = await transformRequest(headers, body, options)
const {
flag,
status,
headers: respHeaders,
body: respBody,
} = await App.Requests(method, url, _headers, _body, _options)
if (!flag) throw respBody
return transformResponse<T>(status, respHeaders, respBody)
}
}
const requestWithoutBody = (
methd: RequestMethod.Get | RequestMethod.Head | RequestMethod.Delete,
) => {
return async <T = any>(
url: string,
headers: Request['headers'] = {},
options: Request['options'] = {},
) => {
const [_headers, , _options] = await transformRequest(headers, null, options)
const {
flag,
status,
headers: respHeaders,
body,
} = await App.Requests(methd, url, _headers, '', _options)
if (!flag) throw body
return transformResponse<T>(status, respHeaders, body)
}
}
interface RequestWithAutoTransform extends Request {
autoTransformBody?: boolean
}
export const Requests = async <T = any>(options: RequestWithAutoTransform) => {
const { method = 'GET', url, headers = {}, body = '', options: reqOpts = {} } = options
const [reqHeaders, reqBody, finalReqOpts] = await transformRequest(headers, body, reqOpts)
const {
flag,
status,
headers: respHeaders,
body: respBody,
} = await App.Requests(method.toUpperCase(), url, reqHeaders, reqBody, finalReqOpts)
if (!flag) throw respBody
const transformedHeaders = transformResponseHeaders(respHeaders)
const transformBody = options.autoTransformBody ?? true
return {
status,
headers: transformedHeaders,
body: transformBody ? transformResponseBody<T>(respBody, transformedHeaders) : (respBody as T),
}
}
export const Upload = requestWithProgress('Upload')
export const Download = requestWithProgress('Download')
export const HttpGet = requestWithoutBody(RequestMethod.Get)
export const HttpHead = requestWithoutBody(RequestMethod.Head)
export const HttpDelete = requestWithoutBody(RequestMethod.Delete)
export const HttpPut = requestWithBody(RequestMethod.Put)
export const HttpPost = requestWithBody(RequestMethod.Post)
export const HttpPatch = requestWithBody(RequestMethod.Patch)
export const HttpCancel = (cancelId: string) => EventsEmit(cancelId)
================================================
FILE: frontend/src/bridge/notification.ts
================================================
import * as App from '@wails/go/bridge/App'
import { APP_TITLE } from '@/utils'
interface NotifyOptions {
AppName?: string
Beep?: boolean
}
export const Notify = async (
title: string,
message: string,
icon = '',
options: NotifyOptions = {},
) => {
const _options: Required<NotifyOptions> = { AppName: APP_TITLE, Beep: true, ...options }
const icons: Record<string, string> = {
success: 'data/.cache/imgs/notify_success.png',
error: 'data/.cache/imgs/notify_error.png',
}
const { flag, data } = await App.Notify(
title,
message,
icons[icon] || 'data/.cache/imgs/tray_normal_dark.png',
_options,
)
if (!flag) {
throw data
}
return data
}
================================================
FILE: frontend/src/bridge/server.ts
================================================
import * as App from '@wails/go/bridge/App'
import { EventsOn, EventsEmit, EventsOff } from '@wails/runtime/runtime'
interface Request {
id: string
method: string
url: string
headers: Record<string, string>
body: string
}
interface Response {
status: number
headers: Record<string, string>
body: string
options: { mode: 'Binary' | 'Text' }
}
interface ServerOptions {
Cert?: string
Key?: string
StaticPath?: string
StaticRoute?: string
StaticHeaders?: Recordable
UploadPath?: string
UploadRoute?: string
UploadHeaders?: Recordable
MaxUploadSize?: number
}
type HttpServerHandler = (
req: Request,
res: {
end: (
status: Response['status'],
headers: Response['headers'],
body: Response['body'],
options: Response['options'],
) => void
},
) => Promise<void>
export const StartServer = async (
address: string,
id: string,
handler: HttpServerHandler,
options: ServerOptions = {},
) => {
const _options: Required<ServerOptions> = {
Cert: '',
Key: '',
StaticPath: '', // default: /static
StaticRoute: '/static/',
StaticHeaders: {},
UploadPath: '', // default: /upload
UploadRoute: '/upload',
UploadHeaders: {},
MaxUploadSize: 50 * 1024 * 1024, // 50MB
...options,
}
const { flag, data } = await App.StartServer(address, id, _options)
if (!flag) {
throw data
}
EventsOn(id, async (...args) => {
const [id, method, url, headers, body] = args
try {
await handler(
{
id,
method,
url,
headers: Object.entries(headers).reduce((p, c: any) => ({ ...p, [c[0]]: c[1][0] }), {}),
body,
},
{
end: (status, headers, body, options = { mode: 'Text' }) => {
EventsEmit(id, status, JSON.stringify(headers), body, JSON.stringify(options))
},
},
)
} catch (err: any) {
console.log('Server handler err:', err, id)
EventsEmit(
id,
500,
JSON.stringify({ 'Content-Type': 'text/plain; charset=utf-8' }),
err.message || err,
JSON.stringify({ Mode: 'Text' }),
)
}
})
return { close: () => StopServer(id) }
}
export const StopServer = async (serverID: string) => {
const { flag, data } = await App.StopServer(serverID)
if (!flag) {
throw data
}
EventsOff(serverID)
return data
}
export const ListServer = async () => {
const { flag, data } = await App.ListServer()
if (!flag) {
throw data
}
return data.split('|').filter((id) => id.length)
}
================================================
FILE: frontend/src/bridge/wailsjs/go/bridge/App.d.ts
================================================
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import {bridge} from '../models';
export function AbsolutePath(arg1:string):Promise<bridge.FlagResult>;
export function CloseMMDB(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function CopyFile(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function Download(arg1:string,arg2:string,arg3:string,arg4:Record<string, string>,arg5:string,arg6:bridge.RequestOptions):Promise<bridge.HTTPResult>;
export function Exec(arg1:string,arg2:Array<string>,arg3:bridge.ExecOptions):Promise<bridge.FlagResult>;
export function ExecBackground(arg1:string,arg2:Array<string>,arg3:string,arg4:string,arg5:bridge.ExecOptions):Promise<bridge.FlagResult>;
export function ExitApp():Promise<void>;
export function FileExists(arg1:string):Promise<bridge.FlagResult>;
export function GetEnv():Promise<bridge.EnvResult>;
export function GetInterfaces():Promise<bridge.FlagResult>;
export function IsStartup():Promise<boolean>;
export function KillProcess(arg1:number,arg2:number):Promise<bridge.FlagResult>;
export function ListServer():Promise<bridge.FlagResult>;
export function MakeDir(arg1:string):Promise<bridge.FlagResult>;
export function MoveFile(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function Notify(arg1:string,arg2:string,arg3:string,arg4:bridge.NotifyOptions):Promise<bridge.FlagResult>;
export function OpenDir(arg1:string):Promise<bridge.FlagResult>;
export function OpenMMDB(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function OpenURI(arg1:string):Promise<bridge.FlagResult>;
export function ProcessInfo(arg1:number):Promise<bridge.FlagResult>;
export function ProcessMemory(arg1:number):Promise<bridge.FlagResult>;
export function QueryMMDB(arg1:string,arg2:string,arg3:string):Promise<bridge.FlagResult>;
export function ReadDir(arg1:string):Promise<bridge.FlagResult>;
export function ReadFile(arg1:string,arg2:bridge.IOOptions):Promise<bridge.FlagResult>;
export function RemoveFile(arg1:string):Promise<bridge.FlagResult>;
export function Requests(arg1:string,arg2:string,arg3:Record<string, string>,arg4:string,arg5:bridge.RequestOptions):Promise<bridge.HTTPResult>;
export function RestartApp():Promise<bridge.FlagResult>;
export function ShowMainWindow():Promise<void>;
export function StartServer(arg1:string,arg2:string,arg3:bridge.ServerOptions):Promise<bridge.FlagResult>;
export function StopServer(arg1:string):Promise<bridge.FlagResult>;
export function UnzipGZFile(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function UnzipTarGZFile(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function UnzipZIPFile(arg1:string,arg2:string):Promise<bridge.FlagResult>;
export function UpdateTray(arg1:bridge.TrayContent):Promise<void>;
export function UpdateTrayAndMenus(arg1:bridge.TrayContent,arg2:Array<bridge.MenuItem>):Promise<void>;
export function UpdateTrayMenus(arg1:Array<bridge.MenuItem>):Promise<void>;
export function Upload(arg1:string,arg2:string,arg3:string,arg4:Record<string, string>,arg5:string,arg6:bridge.RequestOptions):Promise<bridge.HTTPResult>;
export function WriteFile(arg1:string,arg2:string,arg3:bridge.IOOptions):Promise<bridge.FlagResult>;
================================================
FILE: frontend/src/bridge/wailsjs/go/bridge/App.js
================================================
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export function AbsolutePath(arg1) {
return window['go']['bridge']['App']['AbsolutePath'](arg1);
}
export function CloseMMDB(arg1, arg2) {
return window['go']['bridge']['App']['CloseMMDB'](arg1, arg2);
}
export function CopyFile(arg1, arg2) {
return window['go']['bridge']['App']['CopyFile'](arg1, arg2);
}
export function Download(arg1, arg2, arg3, arg4, arg5, arg6) {
return window['go']['bridge']['App']['Download'](arg1, arg2, arg3, arg4, arg5, arg6);
}
export function Exec(arg1, arg2, arg3) {
return window['go']['bridge']['App']['Exec'](arg1, arg2, arg3);
}
export function ExecBackground(arg1, arg2, arg3, arg4, arg5) {
return window['go']['bridge']['App']['ExecBackground'](arg1, arg2, arg3, arg4, arg5);
}
export function ExitApp() {
return window['go']['bridge']['App']['ExitApp']();
}
export function FileExists(arg1) {
return window['go']['bridge']['App']['FileExists'](arg1);
}
export function GetEnv() {
return window['go']['bridge']['App']['GetEnv']();
}
export function GetInterfaces() {
return window['go']['bridge']['App']['GetInterfaces']();
}
export function IsStartup() {
return window['go']['bridge']['App']['IsStartup']();
}
export function KillProcess(arg1, arg2) {
return window['go']['bridge']['App']['KillProcess'](arg1, arg2);
}
export function ListServer() {
return window['go']['bridge']['App']['ListServer']();
}
export function MakeDir(arg1) {
return window['go']['bridge']['App']['MakeDir'](arg1);
}
export function MoveFile(arg1, arg2) {
return window['go']['bridge']['App']['MoveFile'](arg1, arg2);
}
export function Notify(arg1, arg2, arg3, arg4) {
return window['go']['bridge']['App']['Notify'](arg1, arg2, arg3, arg4);
}
export function OpenDir(arg1) {
return window['go']['bridge']['App']['OpenDir'](arg1);
}
export function OpenMMDB(arg1, arg2) {
return window['go']['bridge']['App']['OpenMMDB'](arg1, arg2);
}
export function OpenURI(arg1) {
return window['go']['bridge']['App']['OpenURI'](arg1);
}
export function ProcessInfo(arg1) {
return window['go']['bridge']['App']['ProcessInfo'](arg1);
}
export function ProcessMemory(arg1) {
return window['go']['bridge']['App']['ProcessMemory'](arg1);
}
export function QueryMMDB(arg1, arg2, arg3) {
return window['go']['bridge']['App']['QueryMMDB'](arg1, arg2, arg3);
}
export function ReadDir(arg1) {
return window['go']['bridge']['App']['ReadDir'](arg1);
}
export function ReadFile(arg1, arg2) {
return window['go']['bridge']['App']['ReadFile'](arg1, arg2);
}
export function RemoveFile(arg1) {
return window['go']['bridge']['App']['RemoveFile'](arg1);
}
export function Requests(arg1, arg2, arg3, arg4, arg5) {
return window['go']['bridge']['App']['Requests'](arg1, arg2, arg3, arg4, arg5);
}
export function RestartApp() {
return window['go']['bridge']['App']['RestartApp']();
}
export function ShowMainWindow() {
return window['go']['bridge']['App']['ShowMainWindow']();
}
export function StartServer(arg1, arg2, arg3) {
return window['go']['bridge']['App']['StartServer'](arg1, arg2, arg3);
}
export function StopServer(arg1) {
return window['go']['bridge']['App']['StopServer'](arg1);
}
export function UnzipGZFile(arg1, arg2) {
return window['go']['bridge']['App']['UnzipGZFile'](arg1, arg2);
}
export function UnzipTarGZFile(arg1, arg2) {
return window['go']['bridge']['App']['UnzipTarGZFile'](arg1, arg2);
}
export function UnzipZIPFile(arg1, arg2) {
return window['go']['bridge']['App']['UnzipZIPFile'](arg1, arg2);
}
export function UpdateTray(arg1) {
return window['go']['bridge']['App']['UpdateTray'](arg1);
}
export function UpdateTrayAndMenus(arg1, arg2) {
return window['go']['bridge']['App']['UpdateTrayAndMenus'](arg1, arg2);
}
export function UpdateTrayMenus(arg1) {
return window['go']['bridge']['App']['UpdateTrayMenus'](arg1);
}
export function Upload(arg1, arg2, arg3, arg4, arg5, arg6) {
return window['go']['bridge']['App']['Upload'](arg1, arg2, arg3, arg4, arg5, arg6);
}
export function WriteFile(arg1, arg2, arg3) {
return window['go']['bridge']['App']['WriteFile'](arg1, arg2, arg3);
}
================================================
FILE: frontend/src/bridge/wailsjs/go/models.ts
================================================
export namespace bridge {
export class EnvResult {
appName: string;
appVersion: string;
basePath: string;
os: string;
arch: string;
isPrivileged: boolean;
static createFrom(source: any = {}) {
return new EnvResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.appName = source["appName"];
this.appVersion = source["appVersion"];
this.basePath = source["basePath"];
this.os = source["os"];
this.arch = source["arch"];
this.isPrivileged = source["isPrivileged"];
}
}
export class ExecOptions {
PidFile: string;
StopOutputKeyword: string;
WorkingDirectory: string;
Convert: boolean;
Env: Record<string, string>;
static createFrom(source: any = {}) {
return new ExecOptions(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.PidFile = source["PidFile"];
this.StopOutputKeyword = source["StopOutputKeyword"];
this.WorkingDirectory = source["WorkingDirectory"];
this.Convert = source["Convert"];
this.Env = source["Env"];
}
}
export class FlagResult {
flag: boolean;
data: string;
static createFrom(source: any = {}) {
return new FlagResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.flag = source["flag"];
this.data = source["data"];
}
}
export class HTTPResult {
flag: boolean;
status: number;
headers: Record<string, Array<string>>;
body: string;
static createFrom(source: any = {}) {
return new HTTPResult(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.flag = source["flag"];
this.status = source["status"];
this.headers = source["headers"];
this.body = source["body"];
}
}
export class IOOptions {
Mode: string;
Range: string;
static createFrom(source: any = {}) {
return new IOOptions(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Mode = source["Mode"];
this.Range = source["Range"];
}
}
export class MenuItem {
type: string;
text: string;
tooltip: string;
event: string;
children: MenuItem[];
hidden: boolean;
checked: boolean;
static createFrom(source: any = {}) {
return new MenuItem(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.type = source["type"];
this.text = source["text"];
this.tooltip = source["tooltip"];
this.event = source["event"];
this.children = this.convertValues(source["children"], MenuItem);
this.hidden = source["hidden"];
this.checked = source["checked"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class NotifyOptions {
AppName: string;
Beep: boolean;
static createFrom(source: any = {}) {
return new NotifyOptions(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.AppName = source["AppName"];
this.Beep = source["Beep"];
}
}
export class RequestOptions {
Proxy: string;
Insecure: boolean;
Redirect: boolean;
Timeout: number;
CancelId: string;
FileField: string;
static createFrom(source: any = {}) {
return new RequestOptions(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Proxy = source["Proxy"];
this.Insecure = source["Insecure"];
this.Redirect = source["Redirect"];
this.Timeout = source["Timeout"];
this.CancelId = source["CancelId"];
this.FileField = source["FileField"];
}
}
export class ServerOptions {
Cert: string;
Key: string;
StaticPath: string;
StaticRoute: string;
StaticHeaders: Record<string, string>;
UploadPath: string;
UploadRoute: string;
UploadHeaders: Record<string, string>;
MaxUploadSize: number;
static createFrom(source: any = {}) {
return new ServerOptions(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Cert = source["Cert"];
this.Key = source["Key"];
this.StaticPath = source["StaticPath"];
this.StaticRoute = source["StaticRoute"];
this.StaticHeaders = source["StaticHeaders"];
this.UploadPath = source["UploadPath"];
this.UploadRoute = source["UploadRoute"];
this.UploadHeaders = source["UploadHeaders"];
this.MaxUploadSize = source["MaxUploadSize"];
}
}
export class TrayContent {
icon?: string;
title?: string;
tooltip?: string;
static createFrom(source: any = {}) {
return new TrayContent(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.icon = source["icon"];
this.title = source["title"];
this.tooltip = source["tooltip"];
}
}
}
================================================
FILE: frontend/src/bridge/wailsjs/runtime/package.json
================================================
{
"name": "@wailsapp/runtime",
"version": "2.0.0",
"description": "Wails Javascript runtime library",
"main": "runtime.js",
"types": "runtime.d.ts",
"scripts": {
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}
================================================
FILE: frontend/src/bridge/wailsjs/runtime/runtime.d.ts
================================================
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export interface Position {
x: number;
y: number;
}
export interface Size {
w: number;
h: number;
}
export interface Screen {
isCurrent: boolean;
isPrimary: boolean;
width : number
height : number
}
// Environment information such as platform, buildtype, ...
export interface EnvironmentInfo {
buildType: string;
platform: string;
arch: string;
}
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
// emits the given event. Optional data may be passed with the event.
// This will trigger any event listeners.
export function EventsEmit(eventName: string, ...data: any): void;
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
// sets up a listener for the given event name, but will only trigger a given number times.
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
// sets up a listener for the given event name, but will only trigger once.
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
// unregisters the listener for the given event name.
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
// unregisters all listeners.
export function EventsOffAll(): void;
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
// logs the given message as a raw message
export function LogPrint(message: string): void;
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
// logs the given message at the `trace` log level.
export function LogTrace(message: string): void;
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
// logs the given message at the `debug` log level.
export function LogDebug(message: string): void;
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
// logs the given message at the `error` log level.
export function LogError(message: string): void;
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
// logs the given message at the `fatal` log level.
// The application will quit after calling this method.
export function LogFatal(message: string): void;
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
// logs the given message at the `info` log level.
export function LogInfo(message: string): void;
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
// logs the given message at the `warning` log level.
export function LogWarning(message: string): void;
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
// Forces a reload by the main application as well as connected browsers.
export function WindowReload(): void;
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
// Reloads the application frontend.
export function WindowReloadApp(): void;
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
// Sets the window AlwaysOnTop or not on top.
export function WindowSetAlwaysOnTop(b: boolean): void;
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
// *Windows only*
// Sets window theme to system default (dark/light).
export function WindowSetSystemDefaultTheme(): void;
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
// *Windows only*
// Sets window to light theme.
export function WindowSetLightTheme(): void;
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
// *Windows only*
// Sets window to dark theme.
export function WindowSetDarkTheme(): void;
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
// Centers the window on the monitor the window is currently on.
export function WindowCenter(): void;
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
// Sets the text in the window title bar.
export function WindowSetTitle(title: string): void;
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
// Makes the window full screen.
export function WindowFullscreen(): void;
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
// Restores the previous window dimensions and position prior to full screen.
export function WindowUnfullscreen(): void;
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
export function WindowIsFullscreen(): Promise<boolean>;
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window.
export function WindowSetSize(width: number, height: number): void;
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window.
export function WindowGetSize(): Promise<Size>;
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMaxSize(width: number, height: number): void;
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
// Setting a size of 0,0 will disable this constraint.
export function WindowSetMinSize(width: number, height: number): void;
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
// Sets the window position relative to the monitor the window is currently on.
export function WindowSetPosition(x: number, y: number): void;
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
// Gets the window position relative to the monitor the window is currently on.
export function WindowGetPosition(): Promise<Position>;
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
// Hides the window.
export function WindowHide(): void;
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
// Shows the window, if it is currently hidden.
export function WindowShow(): void;
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
// Maximises the window to fill the screen.
export function WindowMaximise(): void;
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
// Toggles between Maximised and UnMaximised.
export function WindowToggleMaximise(): void;
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
// Restores the window to the dimensions and position prior to maximising.
export function WindowUnmaximise(): void;
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
// Returns the state of the window, i.e. whether the window is maximised or not.
export function WindowIsMaximised(): Promise<boolean>;
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
// Minimises the window.
export function WindowMinimise(): void;
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
// Restores the window to the dimensions and position prior to minimising.
export function WindowUnminimise(): void;
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
// Returns the state of the window, i.e. whether the window is minimised or not.
export function WindowIsMinimised(): Promise<boolean>;
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
// Returns the state of the window, i.e. whether the window is normal or not.
export function WindowIsNormal(): Promise<boolean>;
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
export function ScreenGetAll(): Promise<Screen[]>;
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
// Opens the given URL in the system browser.
export function BrowserOpenURL(url: string): void;
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
// Returns information about the environment
export function Environment(): Promise<EnvironmentInfo>;
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
// Quits the application.
export function Quit(): void;
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
// Hides the application.
export function Hide(): void;
// [Show](https://wails.io/docs/reference/runtime/intro#show)
// Shows the application.
export function Show(): void;
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
// Returns the current text stored on clipboard
export function ClipboardGetText(): Promise<string>;
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
// Sets a text on the clipboard
export function ClipboardSetText(text: string): Promise<boolean>;
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
// OnFileDropOff removes the drag and drop listeners and handlers.
export function OnFileDropOff() :void
// Check if the file path resolver is available
export function CanResolveFilePaths(): boolean;
// Resolves file paths for an array of files
export function ResolveFilePaths(files: File[]): void
================================================
FILE: frontend/src/bridge/wailsjs/runtime/runtime.js
================================================
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
export function LogPrint(message) {
window.runtime.LogPrint(message);
}
export function LogTrace(message) {
window.runtime.LogTrace(message);
}
export function LogDebug(message) {
window.runtime.LogDebug(message);
}
export function LogInfo(message) {
window.runtime.LogInfo(message);
}
export function LogWarning(message) {
window.runtime.LogWarning(message);
}
export function LogError(message) {
window.runtime.LogError(message);
}
export function LogFatal(message) {
window.runtime.LogFatal(message);
}
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
}
export function EventsOn(eventName, callback) {
return EventsOnMultiple(eventName, callback, -1);
}
export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
export function EventsOffAll() {
return window.runtime.EventsOffAll();
}
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
export function EventsEmit(eventName) {
let args = [eventName].slice.call(arguments);
return window.runtime.EventsEmit.apply(null, args);
}
export function WindowReload() {
window.runtime.WindowReload();
}
export function WindowReloadApp() {
window.runtime.WindowReloadApp();
}
export function WindowSetAlwaysOnTop(b) {
window.runtime.WindowSetAlwaysOnTop(b);
}
export function WindowSetSystemDefaultTheme() {
window.runtime.WindowSetSystemDefaultTheme();
}
export function WindowSetLightTheme() {
window.runtime.WindowSetLightTheme();
}
export function WindowSetDarkTheme() {
window.runtime.WindowSetDarkTheme();
}
export function WindowCenter() {
window.runtime.WindowCenter();
}
export function WindowSetTitle(title) {
window.runtime.WindowSetTitle(title);
}
export function WindowFullscreen() {
window.runtime.WindowFullscreen();
}
export function WindowUnfullscreen() {
window.runtime.WindowUnfullscreen();
}
export function WindowIsFullscreen() {
return window.runtime.WindowIsFullscreen();
}
export function WindowGetSize() {
return window.runtime.WindowGetSize();
}
export function WindowSetSize(width, height) {
window.runtime.WindowSetSize(width, height);
}
export function WindowSetMaxSize(width, height) {
window.runtime.WindowSetMaxSize(width, height);
}
export function WindowSetMinSize(width, height) {
window.runtime.WindowSetMinSize(width, height);
}
export function WindowSetPosition(x, y) {
window.runtime.WindowSetPosition(x, y);
}
export function WindowGetPosition() {
return window.runtime.WindowGetPosition();
}
export function WindowHide() {
window.runtime.WindowHide();
}
export function WindowShow() {
window.runtime.WindowShow();
}
export function WindowMaximise() {
window.runtime.WindowMaximise();
}
export function WindowToggleMaximise() {
window.runtime.WindowToggleMaximise();
}
export function WindowUnmaximise() {
window.runtime.WindowUnmaximise();
}
export function WindowIsMaximised() {
return window.runtime.WindowIsMaximised();
}
export function WindowMinimise() {
window.runtime.WindowMinimise();
}
export function WindowUnminimise() {
window.runtime.WindowUnminimise();
}
export function WindowSetBackgroundColour(R, G, B, A) {
window.runtime.WindowSetBackgroundColour(R, G, B, A);
}
export function ScreenGetAll() {
return window.runtime.ScreenGetAll();
}
export function WindowIsMinimised() {
return window.runtime.WindowIsMinimised();
}
export function WindowIsNormal() {
return window.runtime.WindowIsNormal();
}
export function BrowserOpenURL(url) {
window.runtime.BrowserOpenURL(url);
}
export function Environment() {
return window.runtime.Environment();
}
export function Quit() {
window.runtime.Quit();
}
export function Hide() {
window.runtime.Hide();
}
export function Show() {
window.runtime.Show();
}
export function ClipboardGetText() {
return window.runtime.ClipboardGetText();
}
export function ClipboardSetText(text) {
return window.runtime.ClipboardSetText(text);
}
/**
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
*
* @export
* @callback OnFileDropCallback
* @param {number} x - x coordinate of the drop
* @param {number} y - y coordinate of the drop
* @param {string[]} paths - A list of file paths.
*/
/**
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
*
* @export
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
*/
export function OnFileDrop(callback, useDropTarget) {
return window.runtime.OnFileDrop(callback, useDropTarget);
}
/**
* OnFileDropOff removes the drag and drop listeners and handlers.
*/
export function OnFileDropOff() {
return window.runtime.OnFileDropOff();
}
export function CanResolveFilePaths() {
return window.runtime.CanResolveFilePaths();
}
export function ResolveFilePaths(files) {
return window.runtime.ResolveFilePaths(files);
}
================================================
FILE: frontend/src/components/Button/index.vue
================================================
<script setup lang="ts">
import { type IconName } from '@/components/Icon/icons'
interface Props {
type?: 'primary' | 'normal' | 'link' | 'text'
size?: 'default' | 'small' | 'large'
iconSize?: number
iconColor?: string
icon?: IconName
loading?: boolean
disabled?: boolean
}
withDefaults(defineProps<Props>(), {
type: 'normal',
size: 'default',
iconSize: undefined,
iconColor: undefined,
icon: undefined,
loading: false,
disabled: false,
})
</script>
<template>
<div
:class="[type, size, { 'pointer-events-none': disabled || loading }]"
class="gui-button inline-flex items-center justify-center text-center align-middle rounded-6 text-14 text-nowrap cursor-poi
gitextract_dgpyt8y7/ ├── .github/ │ └── workflows/ │ ├── release.yml │ └── rolling-release.yml ├── .gitignore ├── GUI.for.SingBox.code-workspace ├── LICENSE ├── README.md ├── bridge/ │ ├── bridge.go │ ├── exec.go │ ├── exec_others.go │ ├── exec_windows.go │ ├── io.go │ ├── mmdb.go │ ├── net.go │ ├── notification.go │ ├── server.go │ ├── tray.go │ ├── types.go │ └── utils.go ├── build/ │ ├── README.md │ ├── darwin/ │ │ ├── Info.dev.plist │ │ └── Info.plist │ └── windows/ │ ├── info.json │ └── wails.exe.manifest ├── frontend/ │ ├── .editorconfig │ ├── .gitattributes │ ├── .gitignore │ ├── .oxfmtrc.json │ ├── .oxlintrc.json │ ├── .vscode/ │ │ ├── extensions.json │ │ └── settings.json │ ├── env.d.ts │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App.vue │ │ ├── api/ │ │ │ ├── kernel.ts │ │ │ ├── request.ts │ │ │ └── websocket.ts │ │ ├── assets/ │ │ │ ├── globalMethods.ts │ │ │ ├── logo.ts │ │ │ ├── main.less │ │ │ ├── polyfills.ts │ │ │ └── styles/ │ │ │ ├── custom.less │ │ │ ├── reset.less │ │ │ ├── theme.less │ │ │ ├── utilities/ │ │ │ │ ├── display.less │ │ │ │ ├── flex.less │ │ │ │ ├── gap.less │ │ │ │ ├── grid.less │ │ │ │ ├── index.less │ │ │ │ ├── others.less │ │ │ │ ├── rounded.less │ │ │ │ ├── size.less │ │ │ │ ├── spacing.less │ │ │ │ └── text.less │ │ │ └── variables.less │ │ ├── bridge/ │ │ │ ├── app.ts │ │ │ ├── exec.ts │ │ │ ├── index.ts │ │ │ ├── io.ts │ │ │ ├── mmdb.ts │ │ │ ├── net.ts │ │ │ ├── notification.ts │ │ │ ├── server.ts │ │ │ └── wailsjs/ │ │ │ ├── go/ │ │ │ │ ├── bridge/ │ │ │ │ │ ├── App.d.ts │ │ │ │ │ └── App.js │ │ │ │ └── models.ts │ │ │ └── runtime/ │ │ │ ├── package.json │ │ │ ├── runtime.d.ts │ │ │ └── runtime.js │ │ ├── components/ │ │ │ ├── Button/ │ │ │ │ └── index.vue │ │ │ ├── Card/ │ │ │ │ └── index.vue │ │ │ ├── CheckBox/ │ │ │ │ └── index.vue │ │ │ ├── CodeViewer/ │ │ │ │ └── index.vue │ │ │ ├── ColorPicker/ │ │ │ │ └── index.vue │ │ │ ├── Confirm/ │ │ │ │ └── index.vue │ │ │ ├── CustomAction/ │ │ │ │ └── index.vue │ │ │ ├── Divider/ │ │ │ │ └── index.vue │ │ │ ├── Dropdown/ │ │ │ │ └── index.vue │ │ │ ├── Empty/ │ │ │ │ └── index.vue │ │ │ ├── Icon/ │ │ │ │ ├── icons.ts │ │ │ │ └── index.vue │ │ │ ├── Input/ │ │ │ │ └── index.vue │ │ │ ├── InputList/ │ │ │ │ └── index.vue │ │ │ ├── InterfaceSelect/ │ │ │ │ └── index.vue │ │ │ ├── KeyValueEditor/ │ │ │ │ └── index.vue │ │ │ ├── Menu/ │ │ │ │ └── index.vue │ │ │ ├── Message/ │ │ │ │ └── index.vue │ │ │ ├── Modal/ │ │ │ │ ├── index.ts │ │ │ │ └── index.vue │ │ │ ├── MultipleSelect/ │ │ │ │ └── index.vue │ │ │ ├── Pagination/ │ │ │ │ └── index.vue │ │ │ ├── Picker/ │ │ │ │ └── index.vue │ │ │ ├── Progress/ │ │ │ │ └── index.vue │ │ │ ├── Prompt/ │ │ │ │ └── index.vue │ │ │ ├── Radio/ │ │ │ │ └── index.vue │ │ │ ├── Select/ │ │ │ │ └── index.vue │ │ │ ├── Switch/ │ │ │ │ └── index.vue │ │ │ ├── Table/ │ │ │ │ └── index.vue │ │ │ ├── Tabs/ │ │ │ │ └── index.vue │ │ │ ├── Tag/ │ │ │ │ └── index.vue │ │ │ ├── Tips/ │ │ │ │ └── index.vue │ │ │ ├── TrafficChart/ │ │ │ │ └── index.vue │ │ │ ├── _common/ │ │ │ │ ├── AboutView.vue │ │ │ │ ├── CommandView.vue │ │ │ │ ├── NavigationBar.vue │ │ │ │ ├── SplashView.vue │ │ │ │ └── TitleBar.vue │ │ │ ├── components.d.ts │ │ │ └── index.ts │ │ ├── constant/ │ │ │ ├── app.ts │ │ │ ├── kernel.ts │ │ │ └── profile.ts │ │ ├── directives/ │ │ │ ├── index.ts │ │ │ ├── menu.ts │ │ │ ├── platform.ts │ │ │ └── tips.ts │ │ ├── enums/ │ │ │ ├── app.ts │ │ │ └── kernel.ts │ │ ├── hooks/ │ │ │ ├── index.ts │ │ │ ├── useBool.ts │ │ │ └── useCoreBranch.ts │ │ ├── lang/ │ │ │ ├── index.ts │ │ │ └── locale/ │ │ │ ├── en.ts │ │ │ └── zh.ts │ │ ├── main.ts │ │ ├── router/ │ │ │ ├── index.ts │ │ │ ├── router.d.ts │ │ │ └── routes.ts │ │ ├── stores/ │ │ │ ├── app.ts │ │ │ ├── appSettings.ts │ │ │ ├── env.ts │ │ │ ├── index.ts │ │ │ ├── kernelApi.ts │ │ │ ├── logs.ts │ │ │ ├── plugins.ts │ │ │ ├── profiles.ts │ │ │ ├── rulesets.ts │ │ │ ├── scheduledtasks.ts │ │ │ └── subscribes.ts │ │ ├── types/ │ │ │ ├── app.d.ts │ │ │ ├── global.d.ts │ │ │ ├── kernel.d.ts │ │ │ ├── profile.d.ts │ │ │ └── typescript.d.ts │ │ ├── utils/ │ │ │ ├── command.ts │ │ │ ├── completion.ts │ │ │ ├── env.ts │ │ │ ├── eventBus.ts │ │ │ ├── format.ts │ │ │ ├── generator.ts │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ ├── interaction.ts │ │ │ ├── is.ts │ │ │ ├── migration.ts │ │ │ ├── others.ts │ │ │ ├── restorer.ts │ │ │ └── tray.ts │ │ └── views/ │ │ ├── HomeView/ │ │ │ ├── components/ │ │ │ │ ├── CommonController.vue │ │ │ │ ├── ConnectionsController.vue │ │ │ │ ├── GroupsController.vue │ │ │ │ ├── KernelLogs.vue │ │ │ │ ├── LogsController.vue │ │ │ │ ├── OverView.vue │ │ │ │ └── QuickStart.vue │ │ │ └── index.vue │ │ ├── PluginsView/ │ │ │ ├── components/ │ │ │ │ ├── PluginChangelog.vue │ │ │ │ ├── PluginConfigItem.vue │ │ │ │ ├── PluginConfigurator.vue │ │ │ │ ├── PluginForm.vue │ │ │ │ ├── PluginHub.vue │ │ │ │ └── PluginView.vue │ │ │ └── index.vue │ │ ├── ProfilesView/ │ │ │ ├── components/ │ │ │ │ ├── DnsConfig.vue │ │ │ │ ├── DnsRulesConfig.vue │ │ │ │ ├── DnsServersConfig.vue │ │ │ │ ├── GeneralConfig.vue │ │ │ │ ├── InboundsConfig.vue │ │ │ │ ├── MixinAndScriptConfig.vue │ │ │ │ ├── OutboundsConfig.vue │ │ │ │ ├── ProfileEditor.vue │ │ │ │ ├── ProfileForm.vue │ │ │ │ ├── RouteConfig.vue │ │ │ │ ├── RouteRulesConfig.vue │ │ │ │ └── RouteRulesetConfig.vue │ │ │ └── index.vue │ │ ├── RulesetsView/ │ │ │ ├── components/ │ │ │ │ ├── RulesetForm.vue │ │ │ │ ├── RulesetHub.vue │ │ │ │ └── RulesetView.vue │ │ │ └── index.vue │ │ ├── ScheduledTasksView/ │ │ │ ├── components/ │ │ │ │ ├── ScheduledTaskForm.vue │ │ │ │ └── ScheduledTasksLogs.vue │ │ │ └── index.vue │ │ ├── SettingsView/ │ │ │ ├── components/ │ │ │ │ ├── CoreSettings.vue │ │ │ │ ├── GeneralSettings.vue │ │ │ │ ├── PluginSettings.vue │ │ │ │ └── components/ │ │ │ │ ├── AdvancedSettings.vue │ │ │ │ ├── BehaviorSettings.vue │ │ │ │ ├── BranchDetail.vue │ │ │ │ ├── CoreConfig.vue │ │ │ │ ├── FeatureSettings.vue │ │ │ │ ├── PersonalizationSettings.vue │ │ │ │ ├── SwitchBranch.vue │ │ │ │ └── SystemProxySettings.vue │ │ │ └── index.vue │ │ └── SubscribesView/ │ │ ├── components/ │ │ │ ├── ProxiesEditor.vue │ │ │ ├── ProxiesView.vue │ │ │ ├── SubscribeForm.vue │ │ │ └── SubscribeScript.vue │ │ └── index.vue │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── go.mod ├── go.sum ├── main.go └── wails.json
SYMBOL INDEX (395 symbols across 57 files)
FILE: bridge/bridge.go
function NewApp (line 39) | func NewApp() *App {
function CreateApp (line 45) | func CreateApp(fs embed.FS) *App {
method IsStartup (line 80) | func (a *App) IsStartup() bool {
method ExitApp (line 88) | func (a *App) ExitApp() {
method RestartApp (line 94) | func (a *App) RestartApp() FlagResult {
method GetEnv (line 110) | func (a *App) GetEnv() EnvResult {
method GetInterfaces (line 122) | func (a *App) GetInterfaces() FlagResult {
method ShowMainWindow (line 139) | func (a *App) ShowMainWindow() {
function createMacOSSymlink (line 144) | func createMacOSSymlink() {
function createMacOSMenus (line 152) | func createMacOSMenus(app *App) {
function processFixedWebView2Runtime (line 169) | func processFixedWebView2Runtime() {
function extractEmbeddedFiles (line 229) | func extractEmbeddedFiles(fs embed.FS) {
function extractFiles (line 242) | func extractFiles(fs embed.FS, srcDir, dstDir string) {
function loadConfig (line 257) | func loadConfig() {
FILE: bridge/exec.go
method Exec (line 19) | func (a *App) Exec(path string, args []string, options ExecOptions) Flag...
method ExecBackground (line 57) | func (a *App) ExecBackground(path string, args []string, outEvent string...
method ProcessInfo (line 141) | func (a *App) ProcessInfo(pid int32) FlagResult {
method ProcessMemory (line 157) | func (a *App) ProcessMemory(pid int32) FlagResult {
method KillProcess (line 173) | func (a *App) KillProcess(pid int, timeout int) FlagResult {
function waitForProcessExitWithTimeout (line 192) | func waitForProcessExitWithTimeout(process *os.Process, timeoutSeconds i...
FILE: bridge/exec_others.go
function SetCmdWindowHidden (line 13) | func SetCmdWindowHidden(cmd *exec.Cmd) {
function SendExitSignal (line 16) | func SendExitSignal(p *os.Process) error {
function IsProcessAlive (line 20) | func IsProcessAlive(p *os.Process) (bool, error) {
function IsPrivileged (line 39) | func IsPrivileged() (bool, error) {
FILE: bridge/exec_windows.go
constant ATTACH_PARENT_PROCESS (line 15) | ATTACH_PARENT_PROCESS uintptr = ^uintptr(0)
function SetCmdWindowHidden (line 28) | func SetCmdWindowHidden(cmd *exec.Cmd) {
function SendExitSignal (line 35) | func SendExitSignal(p *os.Process) error {
function IsProcessAlive (line 59) | func IsProcessAlive(p *os.Process) (bool, error) {
function IsPrivileged (line 83) | func IsPrivileged() (bool, error) {
FILE: bridge/io.go
constant Binary (line 19) | Binary = "Binary"
constant Text (line 20) | Text = "Text"
method WriteFile (line 23) | func (a *App) WriteFile(path string, content string, options IOOptions) ...
method ReadFile (line 88) | func (a *App) ReadFile(path string, options IOOptions) FlagResult {
method MoveFile (line 129) | func (a *App) MoveFile(source string, target string) FlagResult {
method RemoveFile (line 146) | func (a *App) RemoveFile(path string) FlagResult {
method CopyFile (line 158) | func (a *App) CopyFile(src string, dst string) FlagResult {
method MakeDir (line 187) | func (a *App) MakeDir(path string) FlagResult {
method ReadDir (line 199) | func (a *App) ReadDir(path string) FlagResult {
method OpenDir (line 220) | func (a *App) OpenDir(path string) FlagResult {
method OpenURI (line 233) | func (a *App) OpenURI(uri string) FlagResult {
method AbsolutePath (line 244) | func (a *App) AbsolutePath(path string) FlagResult {
method UnzipZIPFile (line 252) | func (a *App) UnzipZIPFile(path string, output string) FlagResult {
method UnzipTarGZFile (line 306) | func (a *App) UnzipTarGZFile(path string, output string) FlagResult {
method UnzipGZFile (line 368) | func (a *App) UnzipGZFile(path string, output string) FlagResult {
method FileExists (line 399) | func (a *App) FileExists(path string) FlagResult {
FILE: bridge/mmdb.go
method OpenMMDB (line 23) | func (a *App) OpenMMDB(path string, id string) FlagResult {
method CloseMMDB (line 47) | func (a *App) CloseMMDB(path string, id string) FlagResult {
method QueryMMDB (line 75) | func (a *App) QueryMMDB(path string, ip string, dataType string) FlagRes...
function queryByType (line 104) | func queryByType(reader *geoip2.Reader, ip net.IP, dataType string) (any...
FILE: bridge/net.go
method Requests (line 18) | func (a *App) Requests(method string, url string, headers map[string]str...
method Download (line 52) | func (a *App) Download(method string, url string, path string, headers m...
method Upload (line 101) | func (a *App) Upload(method string, url string, path string, headers map...
method Write (line 169) | func (wt *WriteTracker) Write(p []byte) (n int, err error) {
function wrapWithProgress (line 182) | func wrapWithProgress(r io.Reader, size int64, event string, a *App) io....
function withRequestOptionsClient (line 194) | func withRequestOptionsClient(options RequestOptions) (*http.Client, con...
FILE: bridge/notification.go
method Notify (line 7) | func (a *App) Notify(title string, message string, icon string, options ...
FILE: bridge/server.go
type ResponseData (line 26) | type ResponseData struct
method StartServer (line 32) | func (a *App) StartServer(address string, serverID string, options Serve...
method StopServer (line 99) | func (a *App) StopServer(id string) FlagResult {
method ListServer (line 122) | func (a *App) ListServer() FlagResult {
function handleHttpRequest (line 138) | func handleHttpRequest(a *App, serverID string) http.HandlerFunc {
function buildResponse (line 175) | func buildResponse(data []any) ResponseData {
function handleFileDownload (line 204) | func handleFileDownload(w http.ResponseWriter, r *http.Request, fs http....
function handleFileUpload (line 215) | func handleFileUpload(w http.ResponseWriter, r *http.Request, uploadPath...
function handleMultipartUpload (line 238) | func handleMultipartUpload(w http.ResponseWriter, r *http.Request, uploa...
function handleRawUpload (line 279) | func handleRawUpload(w http.ResponseWriter, r *http.Request, uploadPath ...
FILE: bridge/tray.go
function CreateTray (line 11) | func CreateTray(a *App, icon []byte) (trayStart, trayEnd func()) {
method UpdateTray (line 37) | func (a *App) UpdateTray(tray TrayContent) {
method UpdateTrayMenus (line 42) | func (a *App) UpdateTrayMenus(menus []MenuItem) {
method UpdateTrayAndMenus (line 47) | func (a *App) UpdateTrayAndMenus(tray TrayContent, menus []MenuItem) {
function createMenuItem (line 53) | func createMenuItem(menu MenuItem, a *App, parent *systray.MenuItem) {
function updateTray (line 80) | func updateTray(a *App, tray TrayContent) {
function updateTrayMenus (line 96) | func updateTrayMenus(a *App, menus []MenuItem) {
FILE: bridge/types.go
type App (line 11) | type App struct
type EnvResult (line 16) | type EnvResult struct
type RequestOptions (line 29) | type RequestOptions struct
type ExecOptions (line 38) | type ExecOptions struct
type Range (line 46) | type Range struct
type IOOptions (line 51) | type IOOptions struct
type FlagResult (line 56) | type FlagResult struct
type ServerOptions (line 61) | type ServerOptions struct
type NotifyOptions (line 73) | type NotifyOptions struct
type HTTPResult (line 78) | type HTTPResult struct
type AppConfig (line 85) | type AppConfig struct
type TrayContent (line 95) | type TrayContent struct
type WriteTracker (line 101) | type WriteTracker struct
type MenuItem (line 110) | type MenuItem struct
FILE: bridge/utils.go
function GetPath (line 16) | func GetPath(path string) string {
function GetProxy (line 23) | func GetProxy(_proxy string) func(*http.Request) (*url.URL, error) {
function GetTimeout (line 36) | func GetTimeout(timeout int) time.Duration {
function GetHeader (line 43) | func GetHeader(headers map[string]string) http.Header {
function ConvertByte2String (line 51) | func ConvertByte2String(byte []byte) string {
function ParseRange (line 56) | func ParseRange(s string, size int64) (start int64, end int64, err error) {
function RollingRelease (line 121) | func RollingRelease(next http.Handler) http.Handler {
FILE: frontend/env.d.ts
type ImportMetaEnv (line 3) | interface ImportMetaEnv {
type ImportMeta (line 13) | interface ImportMeta {
FILE: frontend/src/api/kernel.ts
type WsKey (line 12) | type WsKey = keyof CoreApiWsDataMap
type WsChannel (line 13) | type WsChannel<K extends WsKey> = {
type Api (line 22) | enum Api {
FILE: frontend/src/api/request.ts
type Method (line 3) | type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
type ResponseType (line 5) | enum ResponseType {
type RequestOptions (line 11) | type RequestOptions = {
class Request (line 19) | class Request {
method constructor (line 26) | constructor(options: RequestOptions = {}) {
FILE: frontend/src/api/websocket.ts
type WebSocketsOptions (line 1) | type WebSocketsOptions = {
type Options (line 7) | type Options = { url: string; cb: (data: any) => void; params?: Record<s...
class WebSockets (line 9) | class WebSockets {
method constructor (line 14) | constructor(options: WebSocketsOptions) {
method createWS (line 20) | public createWS(options: Options) {
FILE: frontend/src/bridge/exec.ts
type ExecOptions (line 6) | interface ExecOptions {
FILE: frontend/src/bridge/io.ts
type IOOptions (line 3) | interface IOOptions {
FILE: frontend/src/bridge/mmdb.ts
type QueryType (line 3) | type QueryType =
FILE: frontend/src/bridge/net.ts
type Request (line 8) | interface Request {
type Response (line 25) | interface Response<T = any> {
type RequestWithProgressOptions (line 89) | interface RequestWithProgressOptions {
type RequestWithAutoTransform (line 177) | interface RequestWithAutoTransform extends Request {
FILE: frontend/src/bridge/notification.ts
type NotifyOptions (line 5) | interface NotifyOptions {
FILE: frontend/src/bridge/server.ts
type Request (line 4) | interface Request {
type Response (line 12) | interface Response {
type ServerOptions (line 19) | interface ServerOptions {
type HttpServerHandler (line 31) | type HttpServerHandler = (
FILE: frontend/src/bridge/wailsjs/go/bridge/App.js
function AbsolutePath (line 5) | function AbsolutePath(arg1) {
function CloseMMDB (line 9) | function CloseMMDB(arg1, arg2) {
function CopyFile (line 13) | function CopyFile(arg1, arg2) {
function Download (line 17) | function Download(arg1, arg2, arg3, arg4, arg5, arg6) {
function Exec (line 21) | function Exec(arg1, arg2, arg3) {
function ExecBackground (line 25) | function ExecBackground(arg1, arg2, arg3, arg4, arg5) {
function ExitApp (line 29) | function ExitApp() {
function FileExists (line 33) | function FileExists(arg1) {
function GetEnv (line 37) | function GetEnv() {
function GetInterfaces (line 41) | function GetInterfaces() {
function IsStartup (line 45) | function IsStartup() {
function KillProcess (line 49) | function KillProcess(arg1, arg2) {
function ListServer (line 53) | function ListServer() {
function MakeDir (line 57) | function MakeDir(arg1) {
function MoveFile (line 61) | function MoveFile(arg1, arg2) {
function Notify (line 65) | function Notify(arg1, arg2, arg3, arg4) {
function OpenDir (line 69) | function OpenDir(arg1) {
function OpenMMDB (line 73) | function OpenMMDB(arg1, arg2) {
function OpenURI (line 77) | function OpenURI(arg1) {
function ProcessInfo (line 81) | function ProcessInfo(arg1) {
function ProcessMemory (line 85) | function ProcessMemory(arg1) {
function QueryMMDB (line 89) | function QueryMMDB(arg1, arg2, arg3) {
function ReadDir (line 93) | function ReadDir(arg1) {
function ReadFile (line 97) | function ReadFile(arg1, arg2) {
function RemoveFile (line 101) | function RemoveFile(arg1) {
function Requests (line 105) | function Requests(arg1, arg2, arg3, arg4, arg5) {
function RestartApp (line 109) | function RestartApp() {
function ShowMainWindow (line 113) | function ShowMainWindow() {
function StartServer (line 117) | function StartServer(arg1, arg2, arg3) {
function StopServer (line 121) | function StopServer(arg1) {
function UnzipGZFile (line 125) | function UnzipGZFile(arg1, arg2) {
function UnzipTarGZFile (line 129) | function UnzipTarGZFile(arg1, arg2) {
function UnzipZIPFile (line 133) | function UnzipZIPFile(arg1, arg2) {
function UpdateTray (line 137) | function UpdateTray(arg1) {
function UpdateTrayAndMenus (line 141) | function UpdateTrayAndMenus(arg1, arg2) {
function UpdateTrayMenus (line 145) | function UpdateTrayMenus(arg1) {
function Upload (line 149) | function Upload(arg1, arg2, arg3, arg4, arg5, arg6) {
function WriteFile (line 153) | function WriteFile(arg1, arg2, arg3) {
FILE: frontend/src/bridge/wailsjs/go/models.ts
class EnvResult (line 3) | class EnvResult {
method createFrom (line 11) | static createFrom(source: any = {}) {
method constructor (line 15) | constructor(source: any = {}) {
class ExecOptions (line 25) | class ExecOptions {
method createFrom (line 32) | static createFrom(source: any = {}) {
method constructor (line 36) | constructor(source: any = {}) {
class FlagResult (line 45) | class FlagResult {
method createFrom (line 49) | static createFrom(source: any = {}) {
method constructor (line 53) | constructor(source: any = {}) {
class HTTPResult (line 59) | class HTTPResult {
method createFrom (line 65) | static createFrom(source: any = {}) {
method constructor (line 69) | constructor(source: any = {}) {
class IOOptions (line 77) | class IOOptions {
method createFrom (line 81) | static createFrom(source: any = {}) {
method constructor (line 85) | constructor(source: any = {}) {
class MenuItem (line 91) | class MenuItem {
method createFrom (line 100) | static createFrom(source: any = {}) {
method constructor (line 104) | constructor(source: any = {}) {
method convertValues (line 115) | convertValues(a: any, classs: any, asMap: boolean = false): any {
class NotifyOptions (line 133) | class NotifyOptions {
method createFrom (line 137) | static createFrom(source: any = {}) {
method constructor (line 141) | constructor(source: any = {}) {
class RequestOptions (line 147) | class RequestOptions {
method createFrom (line 155) | static createFrom(source: any = {}) {
method constructor (line 159) | constructor(source: any = {}) {
class ServerOptions (line 169) | class ServerOptions {
method createFrom (line 180) | static createFrom(source: any = {}) {
method constructor (line 184) | constructor(source: any = {}) {
class TrayContent (line 197) | class TrayContent {
method createFrom (line 202) | static createFrom(source: any = {}) {
method constructor (line 206) | constructor(source: any = {}) {
FILE: frontend/src/bridge/wailsjs/runtime/runtime.d.ts
type Position (line 11) | interface Position {
type Size (line 16) | interface Size {
type Screen (line 21) | interface Screen {
type EnvironmentInfo (line 29) | interface EnvironmentInfo {
FILE: frontend/src/bridge/wailsjs/runtime/runtime.js
function LogPrint (line 11) | function LogPrint(message) {
function LogTrace (line 15) | function LogTrace(message) {
function LogDebug (line 19) | function LogDebug(message) {
function LogInfo (line 23) | function LogInfo(message) {
function LogWarning (line 27) | function LogWarning(message) {
function LogError (line 31) | function LogError(message) {
function LogFatal (line 35) | function LogFatal(message) {
function EventsOnMultiple (line 39) | function EventsOnMultiple(eventName, callback, maxCallbacks) {
function EventsOn (line 43) | function EventsOn(eventName, callback) {
function EventsOff (line 47) | function EventsOff(eventName, ...additionalEventNames) {
function EventsOffAll (line 51) | function EventsOffAll() {
function EventsOnce (line 55) | function EventsOnce(eventName, callback) {
function EventsEmit (line 59) | function EventsEmit(eventName) {
function WindowReload (line 64) | function WindowReload() {
function WindowReloadApp (line 68) | function WindowReloadApp() {
function WindowSetAlwaysOnTop (line 72) | function WindowSetAlwaysOnTop(b) {
function WindowSetSystemDefaultTheme (line 76) | function WindowSetSystemDefaultTheme() {
function WindowSetLightTheme (line 80) | function WindowSetLightTheme() {
function WindowSetDarkTheme (line 84) | function WindowSetDarkTheme() {
function WindowCenter (line 88) | function WindowCenter() {
function WindowSetTitle (line 92) | function WindowSetTitle(title) {
function WindowFullscreen (line 96) | function WindowFullscreen() {
function WindowUnfullscreen (line 100) | function WindowUnfullscreen() {
function WindowIsFullscreen (line 104) | function WindowIsFullscreen() {
function WindowGetSize (line 108) | function WindowGetSize() {
function WindowSetSize (line 112) | function WindowSetSize(width, height) {
function WindowSetMaxSize (line 116) | function WindowSetMaxSize(width, height) {
function WindowSetMinSize (line 120) | function WindowSetMinSize(width, height) {
function WindowSetPosition (line 124) | function WindowSetPosition(x, y) {
function WindowGetPosition (line 128) | function WindowGetPosition() {
function WindowHide (line 132) | function WindowHide() {
function WindowShow (line 136) | function WindowShow() {
function WindowMaximise (line 140) | function WindowMaximise() {
function WindowToggleMaximise (line 144) | function WindowToggleMaximise() {
function WindowUnmaximise (line 148) | function WindowUnmaximise() {
function WindowIsMaximised (line 152) | function WindowIsMaximised() {
function WindowMinimise (line 156) | function WindowMinimise() {
function WindowUnminimise (line 160) | function WindowUnminimise() {
function WindowSetBackgroundColour (line 164) | function WindowSetBackgroundColour(R, G, B, A) {
function ScreenGetAll (line 168) | function ScreenGetAll() {
function WindowIsMinimised (line 172) | function WindowIsMinimised() {
function WindowIsNormal (line 176) | function WindowIsNormal() {
function BrowserOpenURL (line 180) | function BrowserOpenURL(url) {
function Environment (line 184) | function Environment() {
function Quit (line 188) | function Quit() {
function Hide (line 192) | function Hide() {
function Show (line 196) | function Show() {
function ClipboardGetText (line 200) | function ClipboardGetText() {
function ClipboardSetText (line 204) | function ClipboardSetText(text) {
function OnFileDrop (line 225) | function OnFileDrop(callback, useDropTarget) {
function OnFileDropOff (line 232) | function OnFileDropOff() {
function CanResolveFilePaths (line 236) | function CanResolveFilePaths() {
function ResolveFilePaths (line 240) | function ResolveFilePaths(files) {
FILE: frontend/src/components/Icon/icons.ts
type IconName (line 68) | type IconName = keyof typeof icons
FILE: frontend/src/components/Modal/index.ts
method setup (line 20) | setup(_, ctx) {
method setProps (line 34) | setProps(options: Partial<ModalProps> & Recordable) {
method patchProps (line 38) | patchProps(options: Partial<ModalProps> & Recordable) {
method setSlots (line 42) | setSlots(_slots: ModalSlots) {
method patchSlots (line 46) | patchSlots(_slots: ModalSlots) {
method setContent (line 50) | setContent<C extends new (...args: any) => any>(
method setComponent (line 75) | setComponent(comp: VNode) {
FILE: frontend/src/components/components.d.ts
type GlobalComponents (line 4) | interface GlobalComponents {
FILE: frontend/src/directives/index.ts
method install (line 17) | install(app: App) {
FILE: frontend/src/directives/menu.ts
method mounted (line 24) | mounted(el: any, binding: DirectiveBinding) {
method updated (line 27) | updated(el: any, binding: DirectiveBinding) {
FILE: frontend/src/directives/platform.ts
method mounted (line 6) | mounted(el: any, binding: DirectiveBinding) {
FILE: frontend/src/directives/tips.ts
method mounted (line 7) | mounted(el: HTMLElement, binding: DirectiveBinding) {
method beforeUnmount (line 32) | beforeUnmount(el: HTMLElement) {
FILE: frontend/src/enums/app.ts
type WindowStartState (line 1) | enum WindowStartState {
type WebviewGpuPolicy (line 6) | enum WebviewGpuPolicy {
type Theme (line 12) | enum Theme {
type Lang (line 18) | enum Lang {
type View (line 23) | enum View {
type ControllerCloseMode (line 28) | enum ControllerCloseMode {
type Color (line 33) | enum Color {
type Branch (line 40) | enum Branch {
type ScheduledTasksType (line 45) | enum ScheduledTasksType {
type PluginTrigger (line 56) | enum PluginTrigger {
type PluginTriggerEvent (line 71) | enum PluginTriggerEvent {
type RequestMethod (line 90) | enum RequestMethod {
FILE: frontend/src/enums/kernel.ts
type LogLevel (line 1) | enum LogLevel {
type ClashMode (line 11) | enum ClashMode {
type Inbound (line 17) | enum Inbound {
type Outbound (line 24) | enum Outbound {
type TunStack (line 31) | enum TunStack {
type RulesetType (line 37) | enum RulesetType {
type RulesetFormat (line 43) | enum RulesetFormat {
type RuleType (line 48) | enum RuleType {
type Strategy (line 74) | enum Strategy {
type DnsServer (line 82) | enum DnsServer {
type RuleAction (line 95) | enum RuleAction {
type RuleActionReject (line 105) | enum RuleActionReject {
type Sniffer (line 111) | enum Sniffer {
FILE: frontend/src/router/router.d.ts
type RouteMeta (line 6) | interface RouteMeta {
FILE: frontend/src/stores/appSettings.ts
method theme (line 134) | theme(theme: Theme) {
method lang (line 142) | lang(lang: string) {
method color (line 148) | color(color: Color, primary: string, secondary: string) {
method feature (line 155) | feature(outline: boolean, noAnimation: boolean, noRounded: boolean, bord...
method fontFamily (line 161) | fontFamily(fontFamily: string) {
method windowSize (line 164) | windowSize(width: number, height: number) {
method systemProxyBypass (line 168) | systemProxyBypass() {
FILE: frontend/src/stores/kernelApi.ts
type ProxyType (line 48) | type ProxyType = 'mixed' | 'http' | 'socks'
FILE: frontend/src/stores/logs.ts
type TaskLogRecord (line 4) | interface TaskLogRecord<T = any> {
FILE: frontend/src/stores/plugins.ts
method get (line 146) | get(_, p) {
method set (line 165) | set(_, p, newValue) {
method ownKeys (line 178) | ownKeys() {
method getOwnPropertyDescriptor (line 184) | getOwnPropertyDescriptor() {
FILE: frontend/src/stores/rulesets.ts
type RuleSet (line 18) | interface RuleSet {
type RuleSetHub (line 32) | interface RuleSetHub {
FILE: frontend/src/types/app.d.ts
type TrayContent (line 17) | interface TrayContent {
type Menu (line 23) | interface Menu {
type MenuItem (line 30) | interface MenuItem {
type AppSettings (line 40) | interface AppSettings {
type PluginConfiguration (line 104) | interface PluginConfiguration {
type Plugin (line 125) | interface Plugin {
type ScheduledTask (line 155) | interface ScheduledTask {
type Subscription (line 169) | interface Subscription {
type CustomActionApi (line 201) | interface CustomActionApi {
type CustomActionProps (line 205) | type CustomActionProps = Recordable
type CustomActionSlots (line 206) | type CustomActionSlots = Recordable<
type CustomAction (line 209) | interface CustomAction<P = CustomActionProps, S = CustomActionSlots> {
type CustomActionFn (line 215) | type CustomActionFn = ((api: CustomActionApi) => CustomAction) & {
FILE: frontend/src/types/global.d.ts
type Window (line 1) | interface Window {
FILE: frontend/src/types/kernel.d.ts
type CoreApiConfig (line 1) | interface CoreApiConfig {
type CoreApiProxy (line 15) | interface CoreApiProxy {
type CoreApiProxies (line 27) | interface CoreApiProxies {
type CoreApiConnections (line 31) | interface CoreApiConnections {
type CoreApiTrafficData (line 38) | interface CoreApiTrafficData {
type CoreApiMemoryData (line 43) | interface CoreApiMemoryData {
type CoreApiLogsData (line 48) | interface CoreApiLogsData {
type CoreApiConnectionsData (line 53) | interface CoreApiConnectionsData {
type CoreApiWsDataMap (line 79) | type CoreApiWsDataMap = {
FILE: frontend/src/types/profile.d.ts
type LogLevel (line 1) | type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' ...
type ILog (line 2) | interface ILog {
type IExperimental (line 9) | interface IExperimental {
type IProxy (line 30) | interface IProxy {
type RuleSetType (line 36) | type RuleSetType = 'inline' | 'local' | 'remote'
type RuleSetFormat (line 37) | type RuleSetFormat = 'source' | 'binary'
type IRuleSet (line 38) | interface IRuleSet {
type InboundType (line 54) | type InboundType = 'mixed' | 'socks' | 'http' | 'tun'
type InboundListen (line 55) | type InboundListen = {
type IInbound (line 63) | interface IInbound {
type OutboundType (line 93) | type OutboundType = 'direct' | 'block' | 'selector' | 'urltest'
type RuleAction (line 95) | type RuleAction = 'route' | 'route-options' | 'reject' | 'hijack-dns' | ...
type DnsRuleAction (line 96) | type DnsRuleAction = 'route' | 'route-options' | 'reject' | 'predefined'
type IOutbound (line 98) | interface IOutbound {
type RuleType (line 112) | type RuleType =
type IRule (line 136) | interface IRule {
type IRoute (line 152) | interface IRoute {
type Strategy (line 165) | type Strategy = 'default' | 'prefer_ipv4' | 'prefer_ipv6' | 'ipv4_only' ...
type DNSServer (line 166) | type DNSServer =
type IDNSServer (line 179) | interface IDNSServer {
type IDNSRule (line 201) | interface IDNSRule {
type IDNS (line 216) | interface IDNS {
type MixinPriority (line 227) | type MixinPriority = 'mixin' | 'gui'
type IMixin (line 229) | interface IMixin {
type IScript (line 235) | interface IScript {
type IProfile (line 239) | interface IProfile {
FILE: frontend/src/types/typescript.d.ts
type Recordable (line 1) | type Recordable<T = any> = { [x: string]: T }
type MaybePromise (line 3) | type MaybePromise<T> = T | Promise<T>
FILE: frontend/src/utils/command.ts
type Command (line 17) | type Command = {
FILE: frontend/src/utils/env.ts
constant APP_TITLE (line 1) | const APP_TITLE = import.meta.env.VITE_APP_TITLE
constant APP_VERSION (line 3) | const APP_VERSION = import.meta.env.VITE_APP_VERSION
constant APP_VERSION_API (line 5) | const APP_VERSION_API = import.meta.env.VITE_APP_VERSION_API
constant APP_LOCALES_URL (line 7) | const APP_LOCALES_URL = import.meta.env.VITE_APP_LOCALES_URL
constant PROJECT_URL (line 9) | const PROJECT_URL = import.meta.env.VITE_APP_PROJECT_URL
constant TG_GROUP (line 11) | const TG_GROUP = import.meta.env.VITE_APP_TG_GROUP
constant TG_CHANNEL (line 13) | const TG_CHANNEL = import.meta.env.VITE_APP_TG_CHANNEL
FILE: frontend/src/utils/eventBus.ts
type EventMap (line 1) | type EventMap = {
class TypedEventBus (line 9) | class TypedEventBus<Events extends Record<string, any>> {
method on (line 14) | on<K extends keyof Events>(event: K, handler: (data: Events[K]) => voi...
method off (line 20) | off<K extends keyof Events>(event: K, handler: (data: Events[K]) => vo...
method emit (line 26) | emit<K extends keyof Events>(event: K, data: Events[K]) {
FILE: frontend/src/utils/format.ts
function formatBytes (line 3) | function formatBytes(bytes: number, decimals: number = 1): string {
function formatRelativeTime (line 15) | function formatRelativeTime(d: string | number) {
function formatDate (line 44) | function formatDate(timestamp: number | string, format: string) {
FILE: frontend/src/utils/generator.ts
type GenerateConfigOptions (line 376) | type GenerateConfigOptions = {
FILE: frontend/src/utils/helper.ts
function setWindowsSystemProxy (line 122) | async function setWindowsSystemProxy(
function setDarwinSystemProxy (line 167) | async function setDarwinSystemProxy(
function setLinuxSystemProxy (line 230) | async function setLinuxSystemProxy(
function _get (line 487) | async function _get(device: string) {
FILE: frontend/src/utils/interaction.ts
type MessageInstance (line 29) | interface MessageInstance {
class Message (line 39) | class Message {
method constructor (line 43) | constructor() {
class Picker (line 136) | class Picker {
method constructor (line 137) | constructor() {}
FILE: frontend/src/utils/others.ts
type IteratorFn (line 85) | type IteratorFn<T, K> = (item: T, array: T[]) => Promise<K>
type PoolController (line 86) | type PoolController = { pause: () => void; resume: () => void; cancel: (...
type RunPoolOptions (line 87) | interface RunPoolOptions {
function runPool (line 92) | async function runPool<T, K>(
method pause (line 150) | pause() {
method resume (line 153) | resume() {
method cancel (line 158) | cancel() {
method get (line 277) | get(target, key) {
method set (line 284) | set(target, key) {
method deleteProperty (line 288) | deleteProperty(target, key) {
method defineProperty (line 292) | defineProperty(target, key) {
method setPrototypeOf (line 299) | setPrototypeOf(target) {
FILE: frontend/src/utils/restorer.ts
type RestoreProfileOptions (line 41) | type RestoreProfileOptions = {
FILE: frontend/src/utils/tray.ts
function processMenu (line 58) | function processMenu(menu: MenuItem) {
FILE: main.go
function main (line 25) | func main() {
Condensed preview — 219 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (924K chars).
[
{
"path": ".github/workflows/release.yml",
"chars": 5026,
"preview": "name: Build GUI.for.SingBox\n\non:\n push:\n tags:\n - \"v*\"\n\npermissions:\n contents: write\n\njobs:\n Build-Frontend:"
},
{
"path": ".github/workflows/rolling-release.yml",
"chars": 2182,
"preview": "name: Rolling Release\n\non:\n push:\n branches: [main]\n paths:\n - \"frontend/**\"\n\n workflow_dispatch:\n\njobs:\n "
},
{
"path": ".gitignore",
"chars": 34,
"preview": "build/bin\nfrontend/dist\n\n.DS_Store"
},
{
"path": "GUI.for.SingBox.code-workspace",
"chars": 135,
"preview": "{\n \"folders\": [\n {\n \"path\": \".\"\n },\n {\n \"path\": \"frontend\"\n }\n ],\n \"settings\": {\n \"oxc.enabl"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 1020,
"preview": "<div align=\"center\">\n <img src=\"build/appicon.png\" alt=\"GUI.for.SingBox\" width=\"200\">\n <h1>GUI.for.SingBox</h1>\n <p>A"
},
{
"path": "bridge/bridge.go",
"chars": 6044,
"preview": "package bridge\n\nimport (\n\t\"embed\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\n\tsysr"
},
{
"path": "bridge/exec.go",
"chars": 4711,
"preview": "package bridge\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"githu"
},
{
"path": "bridge/exec_others.go",
"chars": 702,
"preview": "//go:build !windows\n\npackage bridge\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"syscall\"\n)\n\nfunc SetCmdWindowHidden(cm"
},
{
"path": "bridge/exec_windows.go",
"chars": 2374,
"preview": "//go:build windows\n\npackage bridge\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"syscall\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n)"
},
{
"path": "bridge/io.go",
"chars": 8686,
"preview": "package bridge\n\nimport (\n\t\"archive/tar\"\n\t\"archive/zip\"\n\t\"compress/gzip\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"p"
},
{
"path": "bridge/mmdb.go",
"chars": 2452,
"preview": "package bridge\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"net\"\n\t\"sync\"\n\n\t\"github.com/oschwald/geoip2-golang\"\n)\n\ntype "
},
{
"path": "bridge/net.go",
"chars": 5367,
"preview": "package bridge\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"log\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filep"
},
{
"path": "bridge/notification.go",
"chars": 459,
"preview": "package bridge\n\nimport (\n\t\"github.com/gen2brain/beeep\"\n)\n\nfunc (a *App) Notify(title string, message string, icon string"
},
{
"path": "bridge/server.go",
"chars": 7448,
"preview": "package bridge\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\""
},
{
"path": "bridge/tray.go",
"chars": 2282,
"preview": "package bridge\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/energye/systray\"\n\t\"github.com/wailsapp/wails/v2/pkg/runtime\"\n)\n\nfunc"
},
{
"path": "bridge/types.go",
"chars": 2547,
"preview": "package bridge\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/wailsapp/wails/v2/pkg/menu\"\n)\n\n// App struct\ntype App stru"
},
{
"path": "bridge/utils.go",
"chars": 3170,
"preview": "package bridge\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang"
},
{
"path": "build/README.md",
"chars": 1591,
"preview": "# Build Directory\n\nThe build directory is used to house all the build files and assets for your application. \n\nThe struc"
},
{
"path": "build/darwin/Info.dev.plist",
"chars": 2308,
"preview": "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1"
},
{
"path": "build/darwin/Info.plist",
"chars": 2229,
"preview": "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1"
},
{
"path": "build/windows/info.json",
"chars": 356,
"preview": "{\n\t\"fixed\": {\n\t\t\"file_version\": \"{{.Info.ProductVersion}}\"\n\t},\n\t\"info\": {\n\t\t\"0000\": {\n\t\t\t\"ProductVersion\": \"{{.Info.Prod"
},
{
"path": "build/windows/wails.exe.manifest",
"chars": 1036,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com"
},
{
"path": "frontend/.editorconfig",
"chars": 170,
"preview": "[*.{ts,vue,less}]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitesp"
},
{
"path": "frontend/.gitattributes",
"chars": 19,
"preview": "* text=auto eol=lf\n"
},
{
"path": "frontend/.gitignore",
"chars": 370,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Stor"
},
{
"path": "frontend/.oxfmtrc.json",
"chars": 217,
"preview": "{\n \"$schema\": \"./node_modules/oxfmt/configuration_schema.json\",\n \"semi\": false,\n \"tabWidth\": 2,\n \"singleQuote\": true"
},
{
"path": "frontend/.oxlintrc.json",
"chars": 367,
"preview": "{\n \"$schema\": \"./node_modules/oxlint/configuration_schema.json\",\n \"plugins\": [\"eslint\", \"typescript\", \"unicorn\", \"oxc\""
},
{
"path": "frontend/.vscode/extensions.json",
"chars": 132,
"preview": "{\n \"recommendations\": [\n \"Vue.volar\",\n \"dbaeumer.vscode-eslint\",\n \"EditorConfig.EditorConfig\",\n \"oxc.oxc-vs"
},
{
"path": "frontend/.vscode/settings.json",
"chars": 374,
"preview": "{\n \"explorer.fileNesting.enabled\": true,\n \"explorer.fileNesting.patterns\": {\n \"tsconfig.json\": \"tsconfig.*.json, en"
},
{
"path": "frontend/env.d.ts",
"chars": 389,
"preview": "/// <reference types=\"vite/client\" />\n\ninterface ImportMetaEnv {\n readonly VITE_APP_TITLE: string\n readonly VITE_APP_V"
},
{
"path": "frontend/eslint.config.js",
"chars": 829,
"preview": "import skipFormatting from 'eslint-config-prettier/flat'\nimport { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslin"
},
{
"path": "frontend/index.html",
"chars": 1357,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" href=\"/favicon.ico\" />\n <"
},
{
"path": "frontend/package.json",
"chars": 1599,
"preview": "{\n \"name\": \"frontend\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite --h"
},
{
"path": "frontend/src/App.vue",
"chars": 3948,
"preview": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport { EventsOn, WindowHide, IsStartup } from '@/bridge'\nimport { "
},
{
"path": "frontend/src/api/kernel.ts",
"chars": 4125,
"preview": "import { Request } from '@/api/request'\nimport { WebSockets } from '@/api/websocket'\nimport { useProfilesStore } from '@"
},
{
"path": "frontend/src/api/request.ts",
"chars": 2481,
"preview": "import { parse } from 'yaml'\n\ntype Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\nenum ResponseType {\n JSON = 'J"
},
{
"path": "frontend/src/api/websocket.ts",
"chars": 1303,
"preview": "type WebSocketsOptions = {\n base?: string\n bearer?: string\n beforeConnect?: () => void\n}\n\ntype Options = { url: strin"
},
{
"path": "frontend/src/assets/globalMethods.ts",
"chars": 428,
"preview": "import * as Vue from 'vue'\nimport { stringify, parse } from 'yaml'\n\nimport * as Bridge from '@/bridge'\nimport * as Store"
},
{
"path": "frontend/src/assets/logo.ts",
"chars": 6728,
"preview": "export default `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACKCAYAAABipUKtAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQ"
},
{
"path": "frontend/src/assets/main.less",
"chars": 160,
"preview": "@import 'styles/variables.less';\n@import 'styles/theme.less';\n@import 'styles/reset.less';\n@import 'styles/custom.less';"
},
{
"path": "frontend/src/assets/polyfills.ts",
"chars": 373,
"preview": "// Polyfill for Promise.withResolvers()\nif (typeof Promise.withResolvers !== 'function') {\n Promise.withResolvers = fun"
},
{
"path": "frontend/src/assets/styles/custom.less",
"chars": 2017,
"preview": "body[feature-outline='true'] {\n * {\n outline: 1px solid var(--color);\n }\n}\n\nbody[feature-no-animation='true'] {\n *"
},
{
"path": "frontend/src/assets/styles/reset.less",
"chars": 513,
"preview": "div,\ninput {\n box-sizing: border-box;\n}\n\ninput {\n font-family: inherit;\n}\n\na {\n text-decoration: none;\n color: var(-"
},
{
"path": "frontend/src/assets/styles/theme.less",
"chars": 3554,
"preview": ":root {\n body[theme-mode='light'] {\n .set-theme(light);\n }\n\n body[theme-mode='dark'] {\n .set-theme(dark);\n }\n}"
},
{
"path": "frontend/src/assets/styles/utilities/display.less",
"chars": 314,
"preview": ".block {\n display: block;\n}\n\n.inline-block {\n display: inline-block;\n}\n\n.inline {\n display: inline;\n}\n\n.flex {\n disp"
},
{
"path": "frontend/src/assets/styles/utilities/flex.less",
"chars": 858,
"preview": ".flex-row {\n flex-direction: row;\n}\n.flex-row-reverse {\n flex-direction: row-reverse;\n}\n.flex-col {\n flex-direction: "
},
{
"path": "frontend/src/assets/styles/utilities/gap.less",
"chars": 322,
"preview": "@gap-values: 0, 2, 4, 8, 10, 12, 16, 24, 32;\n\n.generate-gaps(@i: 1) when (@i <= length(@gap-values)) {\n @v: extract(@ga"
},
{
"path": "frontend/src/assets/styles/utilities/grid.less",
"chars": 323,
"preview": "@cols: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 32, 64;\n\n.generate-cols(@i: 1) when (@i <= length(@cols)) {\n @n: extr"
},
{
"path": "frontend/src/assets/styles/utilities/index.less",
"chars": 199,
"preview": "@import 'spacing.less';\n@import 'gap.less';\n@import 'text.less';\n@import 'display.less';\n@import 'flex.less';\n@import 'g"
},
{
"path": "frontend/src/assets/styles/utilities/others.less",
"chars": 1734,
"preview": ".fixed {\n position: fixed;\n}\n\n.sticky {\n position: sticky;\n}\n\n.relative {\n position: relative;\n}\n\n.absolute {\n posit"
},
{
"path": "frontend/src/assets/styles/utilities/rounded.less",
"chars": 316,
"preview": "@radius-values: 0, 2, 4, 6, 8, 16, 32, 9999;\n\n.generate-rounded(@i: 1) when (@i <= length(@radius-values)) {\n @r: extra"
},
{
"path": "frontend/src/assets/styles/utilities/size.less",
"chars": 939,
"preview": "@size-values: 0, 8, 10, 12, 16, 18, 24, 26, 30, 32, 42, 64, 128, 256;\n@percent-values: 25, 36, 50, 60, 75, 90, 100;\n\n.ge"
},
{
"path": "frontend/src/assets/styles/utilities/spacing.less",
"chars": 2171,
"preview": "@spacing-values: 0, 2, 4, 6, 8, 12, 16, 20, 24, 32, 36;\n@spacing-types: m, p;\n@directions: '', t, r, b, l, x, y;\n\n.gener"
},
{
"path": "frontend/src/assets/styles/utilities/text.less",
"chars": 1196,
"preview": "@text-sizes: 10, 12, 14, 16, 18, 20, 24, 32;\n\n.generate-text(@i: 1) when (@i <= length(@text-sizes)) {\n @s: extract(@te"
},
{
"path": "frontend/src/assets/styles/variables.less",
"chars": 5687,
"preview": ":root {\n --x: 0px;\n --y: 0px;\n\n --primary-color: rgb(0, 89, 214);\n --secondary-color: rgb(5, 62, 142);\n\n // Color &"
},
{
"path": "frontend/src/bridge/app.ts",
"chars": 554,
"preview": "import * as App from '@wails/go/bridge/App'\n\nexport const RestartApp = App.RestartApp\n\nexport const ExitApp = App.ExitAp"
},
{
"path": "frontend/src/bridge/exec.ts",
"chars": 2155,
"preview": "import * as App from '@wails/go/bridge/App'\nimport { EventsOn, EventsOff } from '@wails/runtime/runtime'\n\nimport { sampl"
},
{
"path": "frontend/src/bridge/index.ts",
"chars": 206,
"preview": "export * from '@wails/runtime/runtime'\nexport * from './io'\nexport * from './net'\nexport * from './exec'\nexport * from '"
},
{
"path": "frontend/src/bridge/io.ts",
"chars": 2763,
"preview": "import * as App from '@wails/go/bridge/App'\n\ninterface IOOptions {\n Mode?: 'Binary' | 'Text'\n Range?: string\n}\n\nexport"
},
{
"path": "frontend/src/bridge/mmdb.ts",
"chars": 828,
"preview": "import * as App from '@wails/go/bridge/App'\n\ntype QueryType =\n | 'ASN'\n | 'AnonymousIP'\n | 'City'\n | 'ConnectionType"
},
{
"path": "frontend/src/bridge/net.ts",
"chars": 6147,
"preview": "import * as App from '@wails/go/bridge/App'\nimport { EventsOn, EventsOff, EventsEmit } from '@wails/runtime/runtime'\n\nim"
},
{
"path": "frontend/src/bridge/notification.ts",
"chars": 696,
"preview": "import * as App from '@wails/go/bridge/App'\n\nimport { APP_TITLE } from '@/utils'\n\ninterface NotifyOptions {\n AppName?: "
},
{
"path": "frontend/src/bridge/server.ts",
"chars": 2585,
"preview": "import * as App from '@wails/go/bridge/App'\nimport { EventsOn, EventsEmit, EventsOff } from '@wails/runtime/runtime'\n\nin"
},
{
"path": "frontend/src/bridge/wailsjs/go/bridge/App.d.ts",
"chars": 3308,
"preview": "// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT EDIT\nimport {"
},
{
"path": "frontend/src/bridge/wailsjs/go/bridge/App.js",
"chars": 4241,
"preview": "// @ts-check\n// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL\n// This file is automatically generated. DO NOT "
},
{
"path": "frontend/src/bridge/wailsjs/go/models.ts",
"chars": 6060,
"preview": "export namespace bridge {\n\t\n\texport class EnvResult {\n\t appName: string;\n\t appVersion: string;\n\t basePath: stri"
},
{
"path": "frontend/src/bridge/wailsjs/runtime/package.json",
"chars": 538,
"preview": "{\n \"name\": \"@wailsapp/runtime\",\n \"version\": \"2.0.0\",\n \"description\": \"Wails Javascript runtime library\",\n \"main\": \"r"
},
{
"path": "frontend/src/bridge/wailsjs/runtime/runtime.d.ts",
"chars": 11115,
"preview": "/*\n _ __ _ __\n| | / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__ )\n|__/|__/\\__,_/_/_/"
},
{
"path": "frontend/src/bridge/wailsjs/runtime/runtime.js",
"chars": 5615,
"preview": "/*\n _ __ _ __\n| | / /___ _(_) /____\n| | /| / / __ `/ / / ___/\n| |/ |/ / /_/ / / (__ )\n|__/|__/\\__,_/_/_/"
},
{
"path": "frontend/src/components/Button/index.vue",
"chars": 2555,
"preview": "<script setup lang=\"ts\">\nimport { type IconName } from '@/components/Icon/icons'\n\ninterface Props {\n type?: 'primary' |"
},
{
"path": "frontend/src/components/Card/index.vue",
"chars": 1791,
"preview": "<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport vTips from '@/directives/tips'\n\ninterface Prop"
},
{
"path": "frontend/src/components/CheckBox/index.vue",
"chars": 2154,
"preview": "<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\ninterface Props {\n modelV"
},
{
"path": "frontend/src/components/CodeViewer/index.vue",
"chars": 5891,
"preview": "<script setup lang=\"ts\">\nimport { autocompletion } from '@codemirror/autocomplete'\nimport { indentWithTab } from '@codem"
},
{
"path": "frontend/src/components/ColorPicker/index.vue",
"chars": 1830,
"preview": "<script lang=\"ts\" setup>\nimport { useTemplateRef } from 'vue'\n\ninterface Props {\n disabled?: boolean\n}\n\nconst props = w"
},
{
"path": "frontend/src/components/Confirm/index.vue",
"chars": 5716,
"preview": "<script setup lang=\"ts\">\nimport { marked } from 'marked'\nimport { h, onMounted, ref, render, type VNode } from 'vue'\n\nim"
},
{
"path": "frontend/src/components/CustomAction/index.vue",
"chars": 1730,
"preview": "<script lang=\"ts\" setup>\nimport { computed, h, isVNode, ref, resolveComponent, watch } from 'vue'\n\nimport type { CustomA"
},
{
"path": "frontend/src/components/Divider/index.vue",
"chars": 298,
"preview": "<template>\n <div class=\"flex items-center w-full\">\n <div class=\"flex-1\" style=\"border-top: 1px solid var(--divider-c"
},
{
"path": "frontend/src/components/Dropdown/index.vue",
"chars": 5030,
"preview": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref, watch, nextTick, useTemplateRef } from 'vue'\n\nimport { de"
},
{
"path": "frontend/src/components/Empty/index.vue",
"chars": 538,
"preview": "<script lang=\"ts\" setup>\nimport type { IconName } from '@/components/Icon/icons'\n\ninterface Props {\n icon?: IconName\n "
},
{
"path": "frontend/src/components/Icon/icons.ts",
"chars": 26076,
"preview": "export const icons = {\n messageSuccess: `<svg viewBox=\"64 64 896 896\"><path d=\"M512 64C264.6 64 64 264.6 64 512s200.6 4"
},
{
"path": "frontend/src/components/Icon/index.vue",
"chars": 602,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { icons, type IconName } from './icons'\n\ninterface Props"
},
{
"path": "frontend/src/components/Input/index.vue",
"chars": 5363,
"preview": "<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue'\n\nimport useI18n from '"
},
{
"path": "frontend/src/components/InputList/index.vue",
"chars": 3245,
"preview": "<script setup lang=\"ts\">\nimport { ref, useTemplateRef, watch } from 'vue'\n\nimport { DraggableOptions } from '@/constant/"
},
{
"path": "frontend/src/components/InterfaceSelect/index.vue",
"chars": 659,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\n\nimport { GetInterfaces } from '@/bridge'\n\ninterface Props {\n border"
},
{
"path": "frontend/src/components/KeyValueEditor/index.vue",
"chars": 1634,
"preview": "<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue'\n\ninterface Props {\n modelValue?: Recordable\n placeholder?: ["
},
{
"path": "frontend/src/components/Menu/index.vue",
"chars": 4085,
"preview": "<script lang=\"ts\" setup>\nimport { onMounted, onUnmounted, ref, watch, nextTick, useTemplateRef } from 'vue'\nimport { use"
},
{
"path": "frontend/src/components/Message/index.vue",
"chars": 1125,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport i18n from '@/lang'\n\nexport type MessageIcon = 'info' | '"
},
{
"path": "frontend/src/components/Modal/index.ts",
"chars": 2415,
"preview": "import { ref, defineComponent, h, computed, type VNode, type ComponentPublicInstance } from 'vue'\n\nimport Modal from './"
},
{
"path": "frontend/src/components/Modal/index.vue",
"chars": 6430,
"preview": "<script lang=\"ts\">\nexport const IS_IN_MODAL = 'IS_IN_MODAL'\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, provi"
},
{
"path": "frontend/src/components/MultipleSelect/index.vue",
"chars": 61,
"preview": "<template>\n <Select v-bind=\"$attrs\" multiple />\n</template>\n"
},
{
"path": "frontend/src/components/Pagination/index.vue",
"chars": 1893,
"preview": "<script lang=\"ts\" setup>\nimport { computed } from 'vue'\n\ninterface Props {\n total: number\n size?: 'default' | 'small' "
},
{
"path": "frontend/src/components/Picker/index.vue",
"chars": 3731,
"preview": "<script setup lang=\"ts\" generic=\"ValueType = any, PickerType extends 'single' | 'multi' = 'single'\">\nimport { ref, toRaw"
},
{
"path": "frontend/src/components/Progress/index.vue",
"chars": 1606,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\ninterface Props {\n percent: number\n status?: 'primary' | 'war"
},
{
"path": "frontend/src/components/Prompt/index.vue",
"chars": 1619,
"preview": "<script setup lang=\"ts\">\nimport { ref, unref } from 'vue'\n\nimport useI18n from '@/lang'\n\nimport { type Props as InputPro"
},
{
"path": "frontend/src/components/Radio/index.vue",
"chars": 1706,
"preview": "<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\ninterface Props {\n options?: { label: string; value: strin"
},
{
"path": "frontend/src/components/Select/index.vue",
"chars": 4689,
"preview": "<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { deepClo"
},
{
"path": "frontend/src/components/Switch/index.vue",
"chars": 2580,
"preview": "<script setup lang=\"ts\">\nimport { nextTick } from 'vue'\n\nimport i18n from '@/lang'\n\ninterface Props {\n size?: 'default'"
},
{
"path": "frontend/src/components/Table/index.vue",
"chars": 3865,
"preview": "<script lang=\"ts\" setup>\nimport { ref, computed, isVNode, h } from 'vue'\n\nimport vMenu from '@/directives/menu'\nimport u"
},
{
"path": "frontend/src/components/Tabs/index.vue",
"chars": 1751,
"preview": "<script setup lang=\"ts\">\nimport { computed, useSlots, type Component } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nty"
},
{
"path": "frontend/src/components/Tag/index.vue",
"chars": 1892,
"preview": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\ninterface Props {\n color?: 'cyan' | 'green' | 'red' | 'default' | '"
},
{
"path": "frontend/src/components/Tips/index.vue",
"chars": 1182,
"preview": "<script lang=\"ts\" setup>\nimport { ref, watch, nextTick, useTemplateRef } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\n"
},
{
"path": "frontend/src/components/TrafficChart/index.vue",
"chars": 4431,
"preview": "<script setup lang=\"ts\">\nimport { computed, onMounted, onUnmounted, ref, watch, onActivated, useTemplateRef } from 'vue'"
},
{
"path": "frontend/src/components/_common/AboutView.vue",
"chars": 2082,
"preview": "<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\nimport logo from '@/assets/logo'\nimport { RestartApp, Brows"
},
{
"path": "frontend/src/components/_common/CommandView.vue",
"chars": 4094,
"preview": "<script lang=\"ts\" setup>\nimport { ref, computed, onMounted, onUnmounted, nextTick, watch, useTemplateRef } from 'vue'\nim"
},
{
"path": "frontend/src/components/_common/NavigationBar.vue",
"chars": 836,
"preview": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport rawRoutes from '@/rou"
},
{
"path": "frontend/src/components/_common/SplashView.vue",
"chars": 481,
"preview": "<script setup lang=\"ts\">\nimport logo from '@/assets/logo'\nimport { APP_TITLE, APP_VERSION } from '@/utils/env'\n</script>"
},
{
"path": "frontend/src/components/_common/TitleBar.vue",
"chars": 2985,
"preview": "<script setup lang=\"ts\">\nimport { onMounted, onUnmounted, ref } from 'vue'\n\nimport logo from '@/assets/logo'\nimport {\n "
},
{
"path": "frontend/src/components/components.d.ts",
"chars": 2049,
"preview": "export {}\n\ndeclare module 'vue' {\n export interface GlobalComponents {\n Button: (typeof import('./Button/index.vue')"
},
{
"path": "frontend/src/components/index.ts",
"chars": 704,
"preview": "import type { Plugin, App, Component } from 'vue'\n\nexport { default as TitleBar } from './_common/TitleBar.vue'\nexport {"
},
{
"path": "frontend/src/constant/app.ts",
"chars": 5254,
"preview": "import {\n Color,\n ControllerCloseMode,\n Lang,\n PluginTrigger,\n RequestMethod,\n ScheduledTasksType,\n Theme,\n View"
},
{
"path": "frontend/src/constant/kernel.ts",
"chars": 8645,
"preview": "import {\n ClashMode,\n Inbound,\n Outbound,\n TunStack,\n LogLevel,\n RuleType,\n RulesetFormat,\n RulesetType,\n RuleA"
},
{
"path": "frontend/src/constant/profile.ts",
"chars": 17707,
"preview": "import {\n LogLevel,\n Inbound,\n Outbound,\n TunStack,\n ClashMode,\n RulesetType,\n RulesetFormat,\n RuleType,\n RuleA"
},
{
"path": "frontend/src/directives/index.ts",
"chars": 410,
"preview": "import { vDraggable } from 'vue-draggable-plus'\n\nimport menu from './menu'\nimport platform from './platform'\nimport tips"
},
{
"path": "frontend/src/directives/menu.ts",
"chars": 766,
"preview": "import { useAppStore } from '@/stores'\nimport { sleep } from '@/utils'\n\nimport type { Directive, DirectiveBinding } from"
},
{
"path": "frontend/src/directives/platform.ts",
"chars": 322,
"preview": "import { useEnvStore } from '@/stores'\n\nimport type { Directive, DirectiveBinding } from 'vue'\n\nexport default {\n mount"
},
{
"path": "frontend/src/directives/tips.ts",
"chars": 959,
"preview": "import { type Directive, type DirectiveBinding } from 'vue'\n\nimport { useAppStore } from '@/stores'\nimport { debounce } "
},
{
"path": "frontend/src/enums/app.ts",
"chars": 2016,
"preview": "export enum WindowStartState {\n Normal = 0,\n Minimised = 2,\n}\n\nexport enum WebviewGpuPolicy {\n Always = 0,\n OnDemand"
},
{
"path": "frontend/src/enums/kernel.ts",
"chars": 2233,
"preview": "export enum LogLevel {\n Trace = 'trace',\n Debug = 'debug',\n Info = 'info',\n Warn = 'warn',\n Error = 'error',\n Fata"
},
{
"path": "frontend/src/hooks/index.ts",
"chars": 26,
"preview": "export * from './useBool'\n"
},
{
"path": "frontend/src/hooks/useBool.ts",
"chars": 211,
"preview": "import { ref } from 'vue'\n\nexport const useBool = (initialValue: boolean) => {\n const value = ref(initialValue)\n\n cons"
},
{
"path": "frontend/src/hooks/useCoreBranch.ts",
"chars": 7716,
"preview": "import { computed, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport {\n Download,\n HttpCancel,\n Unzi"
},
{
"path": "frontend/src/lang/index.ts",
"chars": 725,
"preview": "import { createI18n } from 'vue-i18n'\n\nimport { ReadFile } from '@/bridge'\nimport { LocalesFilePath } from '@/constant/a"
},
{
"path": "frontend/src/lang/locale/en.ts",
"chars": 22105,
"preview": "export default {\n common: {\n grid: 'Grid',\n list: 'List',\n add: 'Add',\n added: 'Added',\n more: 'More',\n "
},
{
"path": "frontend/src/lang/locale/zh.ts",
"chars": 17375,
"preview": "export default {\n common: {\n grid: '网格',\n list: '列表',\n add: '添加',\n added: '已添加',\n more: '更多',\n edit: "
},
{
"path": "frontend/src/main.ts",
"chars": 485,
"preview": "import { createPinia } from 'pinia'\nimport { createApp } from 'vue'\n\nimport './assets/main.less'\nimport './assets/polyfi"
},
{
"path": "frontend/src/router/index.ts",
"chars": 221,
"preview": "import { createRouter, createWebHashHistory } from 'vue-router'\n\nimport routes from './routes'\n\nconst router = createRou"
},
{
"path": "frontend/src/router/router.d.ts",
"chars": 200,
"preview": "import 'vue-router'\n\nimport { type IconType } from '@/components/Icon/index.vue'\n\ndeclare module 'vue-router' {\n interf"
},
{
"path": "frontend/src/router/routes.ts",
"chars": 1504,
"preview": "import { type RouteRecordRaw } from 'vue-router'\n\nconst routes: RouteRecordRaw[] = [\n {\n path: '/',\n name: 'Overv"
},
{
"path": "frontend/src/stores/app.ts",
"chars": 6420,
"preview": "import { defineStore } from 'pinia'\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport {\n D"
},
{
"path": "frontend/src/stores/appSettings.ts",
"chars": 7233,
"preview": "import { defineStore } from 'pinia'\nimport { ref, watch } from 'vue'\nimport { parse, stringify } from 'yaml'\n\nimport {\n "
},
{
"path": "frontend/src/stores/env.ts",
"chars": 2520,
"preview": "import { defineStore } from 'pinia'\nimport { ref, watch } from 'vue'\n\nimport { GetEnv } from '@/bridge'\nimport { useAppS"
},
{
"path": "frontend/src/stores/index.ts",
"chars": 267,
"preview": "export * from './appSettings'\nexport * from './profiles'\nexport * from './subscribes'\nexport * from './rulesets'\nexport "
},
{
"path": "frontend/src/stores/kernelApi.ts",
"chars": 14403,
"preview": "import { defineStore } from 'pinia'\nimport { computed, ref, watch } from 'vue'\n\nimport {\n getProxies,\n getConfigs,\n s"
},
{
"path": "frontend/src/stores/logs.ts",
"chars": 874,
"preview": "import { defineStore } from 'pinia'\nimport { computed, ref } from 'vue'\n\ninterface TaskLogRecord<T = any> {\n name: stri"
},
{
"path": "frontend/src/stores/plugins.ts",
"chars": 19161,
"preview": "import { defineStore } from 'pinia'\nimport { computed, ref, watch } from 'vue'\nimport { parse } from 'yaml'\n\nimport { Ht"
},
{
"path": "frontend/src/stores/profiles.ts",
"chars": 2687,
"preview": "import { defineStore } from 'pinia'\nimport { computed, ref } from 'vue'\nimport { parse } from 'yaml'\n\nimport { ReadFile,"
},
{
"path": "frontend/src/stores/rulesets.ts",
"chars": 5940,
"preview": "import { defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { parse } from 'yaml'\n\nimport { ReadFile, WriteFile"
},
{
"path": "frontend/src/stores/scheduledtasks.ts",
"chars": 5733,
"preview": "import { Cron } from 'croner'\nimport { defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { parse } from 'yaml'"
},
{
"path": "frontend/src/stores/subscribes.ts",
"chars": 8318,
"preview": "import { defineStore } from 'pinia'\nimport { ref } from 'vue'\nimport { parse } from 'yaml'\n\nimport { ReadFile, WriteFile"
},
{
"path": "frontend/src/types/app.d.ts",
"chars": 4480,
"preview": "import { h, ref, type VNode } from 'vue'\n\nimport type {\n Lang,\n Theme,\n Color,\n View,\n WindowStartState,\n WebviewG"
},
{
"path": "frontend/src/types/global.d.ts",
"chars": 374,
"preview": "interface Window {\n /**\n * The variable is initialized in `globalMethods.ts:11`\n */\n Plugins: any\n /**\n * The v"
},
{
"path": "frontend/src/types/kernel.d.ts",
"chars": 1437,
"preview": "export interface CoreApiConfig {\n port: number\n 'socks-port': number\n 'mixed-port': number\n 'interface-name': string"
},
{
"path": "frontend/src/types/profile.d.ts",
"chars": 4611,
"preview": "type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'panic'\ninterface ILog {\n disabled: boolean\n "
},
{
"path": "frontend/src/types/typescript.d.ts",
"chars": 85,
"preview": "type Recordable<T = any> = { [x: string]: T }\n\ntype MaybePromise<T> = T | Promise<T>\n"
},
{
"path": "frontend/src/utils/command.ts",
"chars": 7214,
"preview": "import { RestartApp } from '@/bridge'\nimport { ColorOptions, ThemeOptions } from '@/constant/app'\nimport { ModeOptions }"
},
{
"path": "frontend/src/utils/completion.ts",
"chars": 8196,
"preview": "import { snippetCompletion, completeFromList } from '@codemirror/autocomplete'\nimport { scopeCompletionSource, localComp"
},
{
"path": "frontend/src/utils/env.ts",
"chars": 484,
"preview": "export const APP_TITLE = import.meta.env.VITE_APP_TITLE\n\nexport const APP_VERSION = import.meta.env.VITE_APP_VERSION\n\nex"
},
{
"path": "frontend/src/utils/eventBus.ts",
"chars": 918,
"preview": "type EventMap = {\n profileChange: { id: string }\n subscriptionChange: { id: string }\n subscriptionsChange: void\n rul"
},
{
"path": "frontend/src/utils/format.ts",
"chars": 1995,
"preview": "import i18n from '@/lang'\n\nexport function formatBytes(bytes: number, decimals: number = 1): string {\n if (bytes === 0)"
},
{
"path": "frontend/src/utils/generator.ts",
"chars": 15740,
"preview": "import { parse } from 'yaml'\n\nimport { ReadFile, WriteFile } from '@/bridge'\nimport { CoreConfigFilePath } from '@/const"
},
{
"path": "frontend/src/utils/helper.ts",
"chars": 21578,
"preview": "import { deleteConnection, getConnections, useProxy } from '@/api/kernel'\nimport { AbsolutePath, Exec, ExitApp, ReadFile"
},
{
"path": "frontend/src/utils/index.ts",
"chars": 310,
"preview": "export * from './env'\nexport * from './format'\nexport * from './generator'\nexport * from './restorer'\nexport * from './i"
},
{
"path": "frontend/src/utils/interaction.ts",
"chars": 7342,
"preview": "import { render, h, type VNode, nextTick } from 'vue'\n\nimport i18n from '@/lang'\nimport { APP_TITLE, sampleID } from '@/"
},
{
"path": "frontend/src/utils/is.ts",
"chars": 3084,
"preview": "import { Cron } from 'croner'\nimport { parse } from 'yaml'\n\nimport { normalizeBase64 } from './others'\n\nexport const isV"
},
{
"path": "frontend/src/utils/migration.ts",
"chars": 503,
"preview": "export const migrateProfiles = async (profiles: IProfile[], save: () => Promise<string>) => {\n let needSync = false\n\n "
},
{
"path": "frontend/src/utils/others.ts",
"chars": 9738,
"preview": "import { stringify } from 'yaml'\n\nimport { useAppSettingsStore, useEnvStore } from '@/stores'\nimport { APP_TITLE, APP_VE"
},
{
"path": "frontend/src/utils/restorer.ts",
"chars": 14902,
"preview": "import * as Defaults from '@/constant/profile'\nimport {\n Inbound,\n Outbound,\n RuleAction,\n RulesetType,\n RuleType a"
},
{
"path": "frontend/src/utils/tray.ts",
"chars": 9070,
"preview": "import {\n Notify,\n RestartApp,\n EventsOn,\n EventsOff,\n ShowMainWindow,\n UpdateTrayAndMenus,\n} from '@/bridge'\nimpo"
},
{
"path": "frontend/src/views/HomeView/components/CommonController.vue",
"chars": 3845,
"preview": "<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\nimport { TunStackOptions } from '@/constant/kernel'\nimport "
},
{
"path": "frontend/src/views/HomeView/components/ConnectionsController.vue",
"chars": 12206,
"preview": "<script lang=\"ts\" setup>\nimport { ref, computed, onUnmounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { d"
},
{
"path": "frontend/src/views/HomeView/components/GroupsController.vue",
"chars": 13363,
"preview": "<script setup lang=\"ts\">\nimport { ref, computed, onActivated } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { g"
},
{
"path": "frontend/src/views/HomeView/components/KernelLogs.vue",
"chars": 1120,
"preview": "<script setup lang=\"ts\">\nimport { h, withDirectives } from 'vue'\n\nimport { ClipboardSetText } from '@/bridge'\nimport vTi"
},
{
"path": "frontend/src/views/HomeView/components/LogsController.vue",
"chars": 4443,
"preview": "<script lang=\"ts\" setup>\nimport { ref, computed, onUnmounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { L"
},
{
"path": "frontend/src/views/HomeView/components/OverView.vue",
"chars": 7686,
"preview": "<script setup lang=\"ts\">\nimport { ref, onUnmounted, defineAsyncComponent } from 'vue'\nimport { useI18n } from 'vue-i18n'"
},
{
"path": "frontend/src/views/HomeView/components/QuickStart.vue",
"chars": 2245,
"preview": "<script setup lang=\"ts\">\nimport { h, inject, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useProfilesSt"
},
{
"path": "frontend/src/views/HomeView/index.vue",
"chars": 5999,
"preview": "<script setup lang=\"ts\">\nimport { ref, watch, useTemplateRef, defineAsyncComponent } from 'vue'\nimport { useI18n } from "
},
{
"path": "frontend/src/views/PluginsView/components/PluginChangelog.vue",
"chars": 828,
"preview": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\n\nimport { HttpGet, ReadFile } from '@/bridge'\nimport { usePluginsStor"
},
{
"path": "frontend/src/views/PluginsView/components/PluginConfigItem.vue",
"chars": 2496,
"preview": "<script lang=\"ts\" setup>\nimport { ref, watch } from 'vue'\n\nimport { deepClone, message } from '@/utils'\n\nimport type { P"
},
{
"path": "frontend/src/views/PluginsView/components/PluginConfigurator.vue",
"chars": 2681,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, h, useTemplateRef } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport"
},
{
"path": "frontend/src/views/PluginsView/components/PluginForm.vue",
"chars": 10474,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, computed, h } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { Plu"
},
{
"path": "frontend/src/views/PluginsView/components/PluginHub.vue",
"chars": 4555,
"preview": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useBool } from"
},
{
"path": "frontend/src/views/PluginsView/components/PluginView.vue",
"chars": 4669,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, h } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { ReadFile, Wri"
},
{
"path": "frontend/src/views/PluginsView/index.vue",
"chars": 12467,
"preview": "<script setup lang=\"ts\">\nimport { computed, defineAsyncComponent } from 'vue'\nimport { useI18n, I18nT } from 'vue-i18n'\n"
},
{
"path": "frontend/src/views/ProfilesView/components/DnsConfig.vue",
"chars": 2757,
"preview": "<script lang=\"ts\" setup>\nimport { computed, ref, useTemplateRef } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport "
},
{
"path": "frontend/src/views/ProfilesView/components/DnsRulesConfig.vue",
"chars": 10272,
"preview": "<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { DraggableOptio"
},
{
"path": "frontend/src/views/ProfilesView/components/DnsServersConfig.vue",
"chars": 6353,
"preview": "<script lang=\"ts\" setup>\nimport { computed, h, ref, type VNode } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport {"
},
{
"path": "frontend/src/views/ProfilesView/components/GeneralConfig.vue",
"chars": 4218,
"preview": "<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\n\nimport { ModeOptions, LogLevelOptions } from '@/constant/ke"
},
{
"path": "frontend/src/views/ProfilesView/components/InboundsConfig.vue",
"chars": 5679,
"preview": "<script lang=\"ts\" setup>\nimport { useI18n } from 'vue-i18n'\n\nimport { DraggableOptions } from '@/constant/app'\nimport { "
},
{
"path": "frontend/src/views/ProfilesView/components/MixinAndScriptConfig.vue",
"chars": 1884,
"preview": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { parse, stringify } from '"
},
{
"path": "frontend/src/views/ProfilesView/components/OutboundsConfig.vue",
"chars": 11123,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { DraggableOptions } from "
},
{
"path": "frontend/src/views/ProfilesView/components/ProfileEditor.vue",
"chars": 2181,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, h, onMounted } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { ge"
},
{
"path": "frontend/src/views/ProfilesView/components/ProfileForm.vue",
"chars": 7113,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, computed, useTemplateRef, type Ref, h, defineAsyncComponent } from 'vue'\n"
},
{
"path": "frontend/src/views/ProfilesView/components/RouteConfig.vue",
"chars": 2802,
"preview": "<script lang=\"ts\" setup>\nimport { ref, useTemplateRef } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport RouteRules"
},
{
"path": "frontend/src/views/ProfilesView/components/RouteRulesConfig.vue",
"chars": 10638,
"preview": "<script lang=\"ts\" setup>\nimport { computed, ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { DraggableOptio"
},
{
"path": "frontend/src/views/ProfilesView/components/RouteRulesetConfig.vue",
"chars": 5136,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { DraggableOptions } from "
},
{
"path": "frontend/src/views/ProfilesView/index.vue",
"chars": 9000,
"preview": "<script setup lang=\"ts\">\nimport { defineAsyncComponent } from 'vue'\nimport { useI18n, I18nT } from 'vue-i18n'\n\nimport { "
},
{
"path": "frontend/src/views/RulesetsView/components/RulesetForm.vue",
"chars": 4207,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, watch, computed, h } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimpor"
},
{
"path": "frontend/src/views/RulesetsView/components/RulesetHub.vue",
"chars": 6657,
"preview": "<script setup lang=\"ts\">\nimport { computed, h, inject, ref, watch } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimpor"
},
{
"path": "frontend/src/views/RulesetsView/components/RulesetView.vue",
"chars": 1838,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, h } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { ReadFile, Wri"
},
{
"path": "frontend/src/views/RulesetsView/index.vue",
"chars": 8333,
"preview": "<script setup lang=\"ts\">\nimport { computed, defineAsyncComponent } from 'vue'\nimport { useI18n, I18nT } from 'vue-i18n'\n"
},
{
"path": "frontend/src/views/ScheduledTasksView/components/ScheduledTaskForm.vue",
"chars": 7135,
"preview": "<script setup lang=\"ts\">\nimport { ref, inject, h } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { ScheduledTask"
},
{
"path": "frontend/src/views/ScheduledTasksView/components/ScheduledTasksLogs.vue",
"chars": 2962,
"preview": "<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\n\nimport { useLogsStore, "
},
{
"path": "frontend/src/views/ScheduledTasksView/index.vue",
"chars": 5731,
"preview": "<script setup lang=\"ts\">\nimport { Cron } from 'croner'\nimport { defineAsyncComponent } from 'vue'\nimport { useI18n, I18n"
},
{
"path": "frontend/src/views/SettingsView/components/CoreSettings.vue",
"chars": 883,
"preview": "<script setup lang=\"ts\">\nimport { defineAsyncComponent } from 'vue'\n\nimport { useModal } from '@/components/Modal'\n\ncons"
},
{
"path": "frontend/src/views/SettingsView/components/GeneralSettings.vue",
"chars": 592,
"preview": "<script setup lang=\"ts\">\nimport AdvancedSettings from './components/AdvancedSettings.vue'\nimport BehaviorSettings from '"
},
{
"path": "frontend/src/views/SettingsView/components/PluginSettings.vue",
"chars": 2124,
"preview": "<script lang=\"ts\" setup>\nimport { computed } from 'vue'\n\nimport { useAppSettingsStore, usePluginsStore } from '@/stores'"
},
{
"path": "frontend/src/views/SettingsView/components/components/AdvancedSettings.vue",
"chars": 3734,
"preview": "<script lang=\"ts\" setup>\nimport { MakeDir, OpenDir } from '@/bridge'\nimport { RollingReleaseDirectory } from '@/constant"
},
{
"path": "frontend/src/views/SettingsView/components/components/BehaviorSettings.vue",
"chars": 5822,
"preview": "<script lang=\"ts\" setup>\nimport { ref } from 'vue'\n\nimport { WriteFile, RemoveFile, AbsolutePath, ExitApp } from '@/brid"
},
{
"path": "frontend/src/views/SettingsView/components/components/BranchDetail.vue",
"chars": 3401,
"preview": "<script setup lang=\"ts\">\nimport { useI18n } from 'vue-i18n'\n\nimport { RemoveFile } from '@/bridge'\nimport { CoreCacheFil"
}
]
// ... and 19 more files (download for full content)
About this extraction
This page contains the full source code of the GUI-for-Cores/GUI.for.SingBox GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 219 files (847.6 KB), approximately 250.6k tokens, and a symbol index with 395 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.