Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport

This commit is contained in:
Shtorm
2026-02-26 22:44:31 +03:00
parent 0f38dbba4c
commit ded1eb9635
115 changed files with 12582 additions and 301 deletions

View File

@@ -0,0 +1,400 @@
package migration
import (
"database/sql"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
"github.com/sagernet/sing-box/common/migrate/source"
)
var migrations = map[string]string{
"1_initialize_schema.up.sql": `
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
CREATE SEQUENCE public.goadmin_menu_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
SET default_tablespace = '';
SET default_table_access_method = heap;
CREATE TABLE public.goadmin_menu (
id integer DEFAULT nextval('public.goadmin_menu_myid_seq'::regclass) NOT NULL,
parent_id integer DEFAULT 0 NOT NULL,
type integer DEFAULT 0,
"order" integer DEFAULT 0 NOT NULL,
title character varying(50) NOT NULL,
header character varying(100),
plugin_name character varying(100) NOT NULL,
icon character varying(50) NOT NULL,
uri character varying(3000) NOT NULL,
uuid character varying(100),
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_operation_log_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_operation_log (
id integer DEFAULT nextval('public.goadmin_operation_log_myid_seq'::regclass) NOT NULL,
user_id integer NOT NULL,
path character varying(255) NOT NULL,
method character varying(10) NOT NULL,
ip character varying(15) NOT NULL,
input text NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_permissions_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_permissions (
id integer DEFAULT nextval('public.goadmin_permissions_myid_seq'::regclass) NOT NULL,
name character varying(50) NOT NULL,
slug character varying(50) NOT NULL,
http_method character varying(255),
http_path text NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE TABLE public.goadmin_role_menu (
role_id integer NOT NULL,
menu_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE TABLE public.goadmin_role_permissions (
role_id integer NOT NULL,
permission_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE TABLE public.goadmin_role_users (
role_id integer NOT NULL,
user_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_roles_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_roles (
id integer DEFAULT nextval('public.goadmin_roles_myid_seq'::regclass) NOT NULL,
name character varying NOT NULL,
slug character varying NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_session_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_session (
id integer DEFAULT nextval('public.goadmin_session_myid_seq'::regclass) NOT NULL,
sid character varying(50) NOT NULL,
"values" character varying(3000) NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_site_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_site (
id integer DEFAULT nextval('public.goadmin_site_myid_seq'::regclass) NOT NULL,
key character varying(100) NOT NULL,
value text NOT NULL,
type integer DEFAULT 0,
description character varying(3000),
state integer DEFAULT 0,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE TABLE public.goadmin_user_permissions (
user_id integer NOT NULL,
permission_id integer NOT NULL,
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
CREATE SEQUENCE public.goadmin_users_myid_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
MAXVALUE 99999999
CACHE 1;
CREATE TABLE public.goadmin_users (
id integer DEFAULT nextval('public.goadmin_users_myid_seq'::regclass) NOT NULL,
username character varying(100) NOT NULL,
password character varying(100) NOT NULL,
name character varying(100) NOT NULL,
avatar character varying(255),
remember_token character varying(100),
created_at timestamp without time zone DEFAULT now(),
updated_at timestamp without time zone DEFAULT now()
);
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (1, 0, 1, 1, 'Dashboard', NULL, '', 'fa-bar-chart', '/', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (2, 0, 1, 2, 'Admin', NULL, '', 'fa-tasks', '', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (3, 2, 1, 2, 'Users', NULL, '', 'fa-users', '/info/manager', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (4, 2, 1, 3, 'Roles', NULL, '', 'fa-user', '/info/roles', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (5, 2, 1, 4, 'Permission', NULL, '', 'fa-ban', '/info/permission', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (7, 2, 1, 6, 'Operation log', NULL, '', 'fa-history', '/info/op', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (9, 0, 0, 9, 'Users', '', '', 'fa-users', '/info/users', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (14, 0, 0, 12, 'Github', 'Miscellaneous', '', 'fa-github', 'https://github.com/shtorm-7/sing-box-extended', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (15, 0, 0, 13, 'Donate', '', '', 'fa-heart', 'https://github.com/shtorm-7/sing-box-extended#support-the-project', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (13, 0, 0, 7, 'Squads', 'General', '', 'fa-gg', '/info/squads', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (11, 0, 0, 8, 'Nodes', '', '', 'fa-sitemap', '/info/nodes', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (10, 0, 0, 10, 'Connection limiters', 'Limiters', '', 'fa-plug', '/info/connection_limiters', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (8, 0, 0, 11, 'Bandwidth limiters', '', '', 'fa-dashboard', '/info/bandwidth_limiters', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_permissions (id, name, slug, http_method, http_path, created_at, updated_at) VALUES (1, 'All permission', '*', '', '*', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_permissions (id, name, slug, http_method, http_path, created_at, updated_at) VALUES (2, 'Dashboard', 'dashboard', 'GET,PUT,POST,DELETE', '/', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 7, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (2, 7, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (1, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
INSERT INTO public.goadmin_role_users (role_id, user_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_role_users (role_id, user_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_roles (id, name, slug, created_at, updated_at) VALUES (1, 'Administrator', 'administrator', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_roles (id, name, slug, created_at, updated_at) VALUES (2, 'Operator', 'operator', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (6, 'site_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.436501', '2026-02-15 09:57:02.436501');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (7, 'prohibit_config_modification', 'false', 0, NULL, 1, '2026-02-15 09:57:02.441183', '2026-02-15 09:57:02.441183');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (11, 'login_url', '/login', 0, NULL, 1, '2026-02-15 09:57:02.459525', '2026-02-15 09:57:02.459525');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (16, 'open_admin_api', 'false', 0, NULL, 1, '2026-02-15 09:57:02.483908', '2026-02-15 09:57:02.483908');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (18, 'domain', '', 0, NULL, 1, '2026-02-15 09:57:02.493151', '2026-02-15 09:57:02.493151');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (23, 'asset_root_path', './public/', 0, NULL, 1, '2026-02-15 09:57:02.517213', '2026-02-15 09:57:02.517213');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (24, 'url_prefix', 'admin', 0, NULL, 1, '2026-02-15 09:57:02.521815', '2026-02-15 09:57:02.521815');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (33, 'exclude_theme_components', 'null', 0, NULL, 1, '2026-02-15 09:57:02.565725', '2026-02-15 09:57:02.565725');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (39, 'app_id', 'Qn0eh7HQsrt9', 0, NULL, 1, '2026-02-15 09:57:02.592551', '2026-02-15 09:57:02.592551');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (41, 'auth_user_table', 'goadmin_users', 0, NULL, 1, '2026-02-15 09:57:02.601496', '2026-02-15 09:57:02.601496');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (53, 'bootstrap_file_path', '', 0, NULL, 1, '2026-02-15 09:57:02.658984', '2026-02-15 09:57:02.658984');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (55, 'index_url', '/', 0, NULL, 1, '2026-02-15 09:57:02.668457', '2026-02-15 09:57:02.668457');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (66, 'login_logo', '', 0, NULL, 1, '2026-02-15 09:57:02.719608', '2026-02-15 09:57:02.719608');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (67, 'hide_visitor_user_center_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.724307', '2026-02-15 09:57:02.724307');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (68, 'go_mod_file_path', '', 0, NULL, 1, '2026-02-15 09:57:02.728694', '2026-02-15 09:57:02.728694');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (3, 'logger_encoder_caller', 'full', 0, NULL, 1, '2026-02-15 09:57:02.420312', '2026-02-15 09:57:02.420312');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (60, 'logger_encoder_caller_key', 'caller', 0, NULL, 1, '2026-02-15 09:57:02.692189', '2026-02-15 09:57:02.692189');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (34, 'logo', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.570594', '2026-02-15 09:57:02.570594');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (69, 'env', 'prod', 0, NULL, 1, '2026-02-15 09:57:02.733059', '2026-02-15 09:57:02.733059');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (29, 'color_scheme', 'skin-black', 0, NULL, 1, '2026-02-15 09:57:02.545599', '2026-02-15 09:57:02.545599');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (17, 'allow_del_operation_log', 'false', 0, NULL, 1, '2026-02-15 09:57:02.488458', '2026-02-15 09:57:02.488458');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (35, 'info_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.574649', '2026-02-15 09:57:02.574649');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (22, 'operation_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.512394', '2026-02-15 09:57:02.512394');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (42, 'hide_app_info_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.606071', '2026-02-15 09:57:02.606071');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (12, 'access_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.464612', '2026-02-15 09:57:02.464612');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (32, 'logger_rotate_max_age', '30', 0, NULL, 1, '2026-02-15 09:57:02.560801', '2026-02-15 09:57:02.560801');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (40, 'custom_foot_html', '', 0, NULL, 1, '2026-02-15 09:57:02.597285', '2026-02-15 09:57:02.597285');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (62, 'logger_encoder_duration', 'string', 0, NULL, 1, '2026-02-15 09:57:02.701522', '2026-02-15 09:57:02.701522');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (65, 'logger_encoder_level_key', 'level', 0, NULL, 1, '2026-02-15 09:57:02.715108', '2026-02-15 09:57:02.715108');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (64, 'debug', 'false', 0, NULL, 1, '2026-02-15 09:57:02.710705', '2026-02-15 09:57:02.710705');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (43, 'hide_plugin_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.610825', '2026-02-15 09:57:02.610825');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (54, 'animation_type', '', 0, NULL, 1, '2026-02-15 09:57:02.663713', '2026-02-15 09:57:02.663713');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (48, 'theme', 'sword', 0, NULL, 1, '2026-02-15 09:57:02.634039', '2026-02-15 09:57:02.634039');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (45, 'info_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.620165', '2026-02-15 09:57:02.620165');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (31, 'error_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.555798', '2026-02-15 09:57:02.555798');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (5, 'asset_url', '', 0, NULL, 1, '2026-02-15 09:57:02.431855', '2026-02-15 09:57:02.431855');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (36, 'logger_encoder_encoding', 'console', 0, NULL, 1, '2026-02-15 09:57:02.579052', '2026-02-15 09:57:02.579052');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (27, 'login_title', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.536102', '2026-02-15 09:57:02.536102');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (51, 'animation_duration', '0.00', 0, NULL, 1, '2026-02-15 09:57:02.64867', '2026-02-15 09:57:02.64867');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (19, 'file_upload_engine', '{"name":"local"}', 0, NULL, 1, '2026-02-15 09:57:02.49794', '2026-02-15 09:57:02.49794');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (26, 'logger_encoder_time', 'iso8601', 0, NULL, 1, '2026-02-15 09:57:02.531365', '2026-02-15 09:57:02.531365');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (10, 'custom_404_html', '', 0, NULL, 1, '2026-02-15 09:57:02.454777', '2026-02-15 09:57:02.454777');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (58, 'sql_log', 'false', 0, NULL, 1, '2026-02-15 09:57:02.682567', '2026-02-15 09:57:02.682567');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (2, 'logger_encoder_message_key', 'msg', 0, NULL, 1, '2026-02-15 09:57:02.415189', '2026-02-15 09:57:02.415189');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (46, 'logger_encoder_stacktrace_key', 'stacktrace', 0, NULL, 1, '2026-02-15 09:57:02.624977', '2026-02-15 09:57:02.624977');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (63, 'mini_logo', 'SBE', 0, NULL, 1, '2026-02-15 09:57:02.706145', '2026-02-15 09:57:02.706145');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (38, 'custom_403_html', '', 0, NULL, 1, '2026-02-15 09:57:02.588062', '2026-02-15 09:57:02.588062');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (30, 'language', 'en', 0, NULL, 1, '2026-02-15 09:57:02.550466', '2026-02-15 09:57:02.550466');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (15, 'hide_config_center_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.479097', '2026-02-15 09:57:02.479097');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (59, 'logger_rotate_max_backups', '5', 0, NULL, 1, '2026-02-15 09:57:02.687429', '2026-02-15 09:57:02.687429');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (57, 'custom_head_html', '', 0, NULL, 1, '2026-02-15 09:57:02.677723', '2026-02-15 09:57:02.677723');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (52, 'custom_500_html', '', 0, NULL, 1, '2026-02-15 09:57:02.654236', '2026-02-15 09:57:02.654236');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (44, 'title', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.615471', '2026-02-15 09:57:02.615471');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (47, 'session_life_time', '7200', 0, NULL, 1, '2026-02-15 09:57:02.629619', '2026-02-15 09:57:02.629619');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (8, 'access_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.445593', '2026-02-15 09:57:02.445593');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (49, 'error_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.6385', '2026-02-15 09:57:02.6385');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (50, 'logger_rotate_max_size', '10', 0, NULL, 1, '2026-02-15 09:57:02.643733', '2026-02-15 09:57:02.643733');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (14, 'logger_rotate_compress', 'false', 0, NULL, 1, '2026-02-15 09:57:02.474296', '2026-02-15 09:57:02.474296');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (13, 'logger_encoder_time_key', 'ts', 0, NULL, 1, '2026-02-15 09:57:02.469396', '2026-02-15 09:57:02.469396');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (37, 'animation_delay', '0.00', 0, NULL, 1, '2026-02-15 09:57:02.583815', '2026-02-15 09:57:02.583815');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (20, 'extra', '', 0, NULL, 1, '2026-02-15 09:57:02.50276', '2026-02-15 09:57:02.50276');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (25, 'access_assets_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.526618', '2026-02-15 09:57:02.526618');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (4, 'logger_level', '0', 0, NULL, 1, '2026-02-15 09:57:02.426736', '2026-02-15 09:57:02.426736');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (9, 'footer_info', '', 0, NULL, 1, '2026-02-15 09:57:02.450409', '2026-02-15 09:57:02.450409');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (21, 'no_limit_login_ip', 'false', 0, NULL, 1, '2026-02-15 09:57:02.507609', '2026-02-15 09:57:02.507609');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (28, 'hide_tool_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.540813', '2026-02-15 09:57:02.540813');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (61, 'logger_encoder_level', 'capitalColor', 0, NULL, 1, '2026-02-15 09:57:02.696859', '2026-02-15 09:57:02.696859');
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (56, 'logger_encoder_name_key', 'logger', 0, NULL, 1, '2026-02-15 09:57:02.672962', '2026-02-15 09:57:02.672962');
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
INSERT INTO public.goadmin_users (id, username, password, name, avatar, remember_token, created_at, updated_at) VALUES (2, 'operator', '$2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh.', 'Operator', '', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
INSERT INTO public.goadmin_users (id, username, password, name, avatar, remember_token, created_at, updated_at) VALUES (1, 'admin', '$2a$10$ilNHHnX5S6EMw.Ffc1Y1JezYCyquFIO.7Z0vLr1eHJUXnGy4cdrtq', 'admin', '', 'tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
SELECT pg_catalog.setval('public.goadmin_menu_myid_seq', 12, true);
SELECT pg_catalog.setval('public.goadmin_operation_log_myid_seq', 11, true);
SELECT pg_catalog.setval('public.goadmin_permissions_myid_seq', 2, true);
SELECT pg_catalog.setval('public.goadmin_roles_myid_seq', 2, true);
SELECT pg_catalog.setval('public.goadmin_session_myid_seq', 7, true);
SELECT pg_catalog.setval('public.goadmin_site_myid_seq', 69, true);
SELECT pg_catalog.setval('public.goadmin_users_myid_seq', 2, true);
ALTER TABLE ONLY public.goadmin_menu
ADD CONSTRAINT goadmin_menu_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_operation_log
ADD CONSTRAINT goadmin_operation_log_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_permissions
ADD CONSTRAINT goadmin_permissions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_roles
ADD CONSTRAINT goadmin_roles_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_session
ADD CONSTRAINT goadmin_session_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_site
ADD CONSTRAINT goadmin_site_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.goadmin_users
ADD CONSTRAINT goadmin_users_pkey PRIMARY KEY (id);
`,
"1_initialize_schema.down.sql": ``,
}
func MigratePostgreSQL(db *sql.DB) error {
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
return err
}
sourceDriver := source.NewRawDriver(migrations)
if err := sourceDriver.Init(); err != nil {
return err
}
m, err := migrate.NewWithInstance(
"raw",
sourceDriver,
"postgres",
driver,
)
if err != nil {
return err
}
return m.Up()
}

View File

@@ -0,0 +1,13 @@
package pages
import (
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/template/types"
)
func DashboardPage(ctx *context.Context) (types.Panel, error) {
return types.Panel{
Title: "Dashboard",
}, nil
}

View File

@@ -0,0 +1,188 @@
//go:build with_admin_panel
package admin_panel
import (
"context"
"database/sql"
"errors"
"net/http"
"github.com/go-chi/chi"
"github.com/golang-migrate/migrate/v4"
_ "github.com/lib/pq"
"golang.org/x/net/http2"
_ "github.com/GoAdminGroup/go-admin/adapter/chi"
"github.com/GoAdminGroup/go-admin/engine"
"github.com/GoAdminGroup/go-admin/modules/config"
_ "github.com/GoAdminGroup/go-admin/modules/db/drivers/sqlite"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/chartjs"
_ "github.com/GoAdminGroup/themes/adminlte"
_ "github.com/GoAdminGroup/themes/sword"
"github.com/sagernet/sing-box/adapter"
boxService "github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/service/admin_panel/migration"
"github.com/sagernet/sing-box/service/admin_panel/pages"
"github.com/sagernet/sing-box/service/admin_panel/tables"
CM "github.com/sagernet/sing-box/service/manager/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
"github.com/sagernet/sing/service"
)
func RegisterService(registry *boxService.Registry) {
boxService.Register[option.AdminPanelServiceOptions](registry, C.TypeAdminPanel, NewService)
}
type Service struct {
boxService.Adapter
ctx context.Context
logger log.ContextLogger
listener *listener.Listener
tlsConfig tls.ServerConfig
httpServer *http.Server
options option.AdminPanelServiceOptions
}
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.AdminPanelServiceOptions) (adapter.Service, error) {
s := &Service{
Adapter: boxService.NewAdapter(C.TypeAdminPanel, tag),
ctx: ctx,
logger: logger,
listener: listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: []string{N.NetworkTCP},
Listen: options.ListenOptions,
}),
options: options,
}
return s, nil
}
func (s *Service) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
service, ok := boxManager.Get(s.options.Manager)
if !ok {
return E.New("manager ", s.options.Manager, " not found")
}
manager, ok := service.(CM.Manager)
if !ok {
return E.New("invalid ", s.options.Manager, " manager")
}
switch s.options.Database.Driver {
case "postgresql":
db, err := sql.Open("postgres", s.options.Database.DSN)
if err != nil {
return err
}
defer db.Close()
if err := migration.MigratePostgreSQL(db); err != nil && err != migrate.ErrNoChange {
return err
}
default:
return E.New("unknown driver \"", s.options.Database.Driver, "\"")
}
var generators = map[string]table.Generator{
"squads": tables.SquadTableFactory(
manager,
s.logger,
),
"nodes": tables.NodeTableFactory(
manager,
s.logger,
),
"users": tables.UserTableFactory(
manager,
s.logger,
),
"connection_limiters": tables.ConnectionLimiterTableFactory(
manager,
s.logger,
),
"bandwidth_limiters": tables.BandwidthLimiterTableFactory(
manager,
s.logger,
),
}
eng := engine.Default()
chiRouter := chi.NewRouter()
template.AddComp(chartjs.NewChart())
if err := eng.AddConfig(&config.Config{
UrlPrefix: "admin",
IndexUrl: "/",
LoginUrl: "/login",
Databases: config.DatabaseList{
"default": config.Database{
Driver: s.options.Database.Driver,
Dsn: s.options.Database.DSN,
},
},
}).
AddGenerators(generators).
Use(chiRouter); err != nil {
return err
}
eng.HTML("GET", "/admin", pages.DashboardPage)
chiRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin", http.StatusMovedPermanently)
})
chiRouter.Get("/admin/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin", http.StatusMovedPermanently)
})
if s.options.TLS != nil {
tlsConfig, err := tls.NewServer(s.ctx, s.logger, common.PtrValueOrDefault(s.options.TLS))
if err != nil {
return err
}
s.tlsConfig = tlsConfig
}
if s.tlsConfig != nil {
err := s.tlsConfig.Start()
if err != nil {
return E.Cause(err, "create TLS config")
}
}
tcpListener, err := s.listener.ListenTCP()
if err != nil {
return err
}
if s.tlsConfig != nil {
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
}
tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)
}
s.httpServer = &http.Server{
Handler: chiRouter,
}
go func() {
err = s.httpServer.Serve(tcpListener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
s.logger.Error("serve error: ", err)
}
}()
return nil
}
func (s *Service) Close() error {
return common.Close(
common.PtrOrNil(s.httpServer),
common.PtrOrNil(s.listener),
s.tlsConfig,
)
}

View File

@@ -0,0 +1,20 @@
//go:build !with_admin_panel
package admin_panel
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/service"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func RegisterService(registry *service.Registry) {
service.Register[option.AdminPanelServiceOptions](registry, C.TypeAdminPanel, func(ctx context.Context, logger log.ContextLogger, tag string, options option.AdminPanelServiceOptions) (adapter.Service, error) {
return nil, E.New(`Admin panel is not included in this build, rebuild with -tags with_admin_panel`)
})
}

View File

@@ -0,0 +1,259 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func BandwidthLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
return func(ctx *context.Context) table.Table {
t := table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := t.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Outbound", "outbound", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Strategy", "strategy", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Connection", Value: "connection"},
{Text: "Global", Value: "global"},
},
}).
FieldSortable()
info.AddField("Mode", "mode", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Download", Value: "download"},
{Text: "Upload", Value: "upload"},
{Text: "Duplex", Value: "duplex"},
},
}).
FieldSortable()
info.AddField("Connection type", "connection_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "HWID", Value: "hwid"},
{Text: "Mux", Value: "mux"},
{Text: "IP", Value: "ip"},
},
}).
FieldSortable()
info.AddField("Speed", "speed", db.Varchar).
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string)
listFilters := map[string][]string{
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
"limit": {param.PageSize},
}
for k, v := range param.Fields {
if strings.HasPrefix(k, "__") {
continue
}
key := strings.TrimSuffix(k, "__goadmin")
filters[key] = v
listFilters[key] = v
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
items, err := manager.GetBandwidthLimiters(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetBandwidthLimitersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
var data map[string]interface{}
raw, _ := json.Marshal(item)
json.Unmarshal(raw, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
i, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteBandwidthLimiter(i); err != nil {
return err
}
}
return nil
})
info.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
formList := t.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Connection", Value: "connection"},
{Text: "Global", Value: "global"},
}).
FieldOnChooseOptionsHide([]string{"", "global"}, "connection_type")
formList.AddField("Mode", "mode", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Download", Value: "download"},
{Text: "Upload", Value: "upload"},
{Text: "Duplex", Value: "duplex"},
})
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "HWID", Value: "hwid"},
{Text: "Mux", Value: "mux"},
{Text: "IP", Value: "ip"},
})
formList.AddField("Speed", "speed", db.Varchar, form.Text).
FieldMust()
formList.SetInsertFn(func(values mForm.Values) error {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
_, err := manager.CreateBandwidthLimiter(CM.BandwidthLimiterCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
Mode: values.Get("mode"),
ConnectionType: values.Get("connection_type"),
Speed: values.Get("speed"),
})
return err
})
formList.SetUpdateFn(func(values mForm.Values) error {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
_, err = manager.UpdateBandwidthLimiter(id, CM.BandwidthLimiterUpdate{
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
Mode: values.Get("mode"),
ConnectionType: values.Get("connection_type"),
Speed: values.Get("speed"),
})
return err
})
formList.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
return t
}
}

View File

@@ -0,0 +1,261 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func ConnectionLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
return func(ctx *context.Context) table.Table {
connectionLimiterTable := table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := connectionLimiterTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Outbound", "outbound", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Strategy", "strategy", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Connection", Value: "connection"},
},
}).
FieldSortable()
info.AddField("Connection type", "connection_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Mux", Value: "mux"},
{Text: "HWID", Value: "hwid"},
{Text: "IP", Value: "ip"},
},
}).
FieldSortable()
info.AddField("Lock type", "lock_type", db.Varchar).
FieldFilterable(types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Manager", Value: "manager"},
},
}).
FieldSortable()
info.AddField("Count", "count", db.Int).
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string)
listFilters := map[string][]string{
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
"limit": {param.PageSize},
}
for k, v := range param.Fields {
if strings.HasPrefix(k, "__") {
continue
}
key := strings.TrimSuffix(k, "__goadmin")
filters[key] = v
listFilters[key] = v
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
items, err := manager.GetConnectionLimiters(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetConnectionLimitersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
var data map[string]interface{}
raw, _ := json.Marshal(item)
json.Unmarshal(raw, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
i, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteConnectionLimiter(i); err != nil {
return err
}
}
return nil
})
info.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
formList := connectionLimiterTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
FieldMust().
FieldOptions(types.FieldOptions{
{Text: "Connection", Value: "connection"},
}).
FieldDefault("connection")
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "Mux", Value: "mux"},
{Text: "HWID", Value: "hwid"},
{Text: "IP", Value: "ip"},
})
formList.AddField("Lock type", "lock_type", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "Manager", Value: "manager"},
})
formList.AddField("Count", "count", db.Int, form.Number).
FieldMust().
FieldDefault("0")
formList.SetInsertFn(func(values mForm.Values) error {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
if err != nil {
return err
}
_, err = manager.CreateConnectionLimiter(CM.ConnectionLimiterCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
ConnectionType: values.Get("connection_type"),
LockType: values.Get("lock_type"),
Count: uint32(count),
})
return err
})
formList.SetUpdateFn(func(values mForm.Values) error {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
if err != nil {
return err
}
_, err = manager.UpdateConnectionLimiter(id, CM.ConnectionLimiterUpdate{
Username: values.Get("username"),
Outbound: values.Get("outbound"),
Strategy: values.Get("strategy"),
ConnectionType: values.Get("connection_type"),
LockType: values.Get("lock_type"),
Count: uint32(count),
})
return err
})
formList.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
return connectionLimiterTable
}
}

View File

@@ -0,0 +1,201 @@
package tables
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/config"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/gofrs/uuid/v5"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func label(ctx *context.Context) types.LabelAttribute {
return template.Get(ctx, config.GetTheme()).Label().SetType("success")
}
func NodeTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (nodeTable table.Table) {
return func(ctx *context.Context) (nodeTable table.Table) {
nodeTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Varchar,
Name: "uuid",
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := nodeTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("UUID", "uuid", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Name", "name", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Status", "status", db.Varchar).
FieldDisplay(func(value types.FieldModel) interface{} {
uuid := value.Row["uuid"].(string)
return manager.GetNodeStatus(uuid)
})
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "uuid"
} else {
if strings.HasPrefix(key, "__") {
continue
}
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
nodes, err := manager.GetNodes(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetNodesCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(nodes))
for _, node := range nodes {
var data map[string]interface{}
rawData, _ := json.Marshal(node)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, uuid := range ids {
if _, err := manager.DeleteNode(uuid); err != nil {
return err
}
}
return nil
})
info.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
defaultUUID, _ := uuid.NewV4()
formList := nodeTable.GetForm()
formList.AddField("UUID", "uuid", db.Varchar, form.Text).
FieldMust().
FieldNotAllowEdit().
FieldDefault(defaultUUID.String())
formList.AddField("Name", "name", db.Varchar, form.Text).
FieldMust()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.SetInsertFn(func(values mForm.Values) (err error) {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
_, err = manager.CreateNode(CM.NodeCreate{
UUID: values.Get("uuid"),
Name: values.Get("name"),
SquadIDs: squadIDs,
})
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
uuid := values.Get("uuid")
_, err = manager.UpdateNode(uuid, CM.NodeUpdate{
Name: values.Get("name"),
})
return
})
formList.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
return
}
}

View File

@@ -0,0 +1,164 @@
package tables
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/go-playground/validator/v10"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func SquadTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (squadTable table.Table) {
return func(ctx *context.Context) (squadTable table.Table) {
squadTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
info := squadTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Name", "name", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Created At", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldSortable().
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
info.AddField("Updated At", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldSortable().
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "pk"
} else if strings.HasPrefix(key, "__") {
continue
} else {
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
squads, err := manager.GetSquads(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetSquadsCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(squads))
for _, squad := range squads {
var data map[string]interface{}
rawData, _ := json.Marshal(squad)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
intID, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteSquad(intID); err != nil {
return err
}
}
return nil
})
info.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
formList := squadTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowAdd().
FieldNotAllowEdit()
formList.AddField("Name", "name", db.Varchar, form.Text).
FieldMust()
formList.SetInsertFn(func(values mForm.Values) (err error) {
_, err = manager.CreateSquad(CM.SquadCreate{
Name: values.Get("name"),
})
if err != nil {
if ve, ok := err.(validator.ValidationErrors); ok {
var errors []string
for _, e := range ve {
switch e.Tag() {
case "required":
errors = append(errors, e.StructField()+": required field missing")
default:
errors = append(errors, e.StructField()+": invalid request")
}
}
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
}
}
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
_, err = manager.UpdateSquad(id, CM.SquadUpdate{
Name: values.Get("name"),
})
return
})
formList.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
return
}
}

View File

@@ -0,0 +1,282 @@
package tables
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
"github.com/GoAdminGroup/go-admin/template"
"github.com/GoAdminGroup/go-admin/template/types"
"github.com/GoAdminGroup/go-admin/template/types/form"
"github.com/go-playground/validator/v10"
"github.com/sagernet/sing-box/log"
CM "github.com/sagernet/sing-box/service/manager/constant"
)
func UserTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (userTable table.Table) {
return func(ctx *context.Context) (userTable table.Table) {
userTable = table.NewDefaultTable(ctx, table.Config{
CanAdd: true,
Editable: true,
Deletable: true,
Exportable: true,
PrimaryKey: table.PrimaryKey{
Type: db.Int,
Name: table.DefaultPrimaryKeyName,
},
})
squads, err := manager.GetSquads(map[string][]string{})
if err != nil {
return nil
}
squadsByID := make(map[int]string, len(squads))
squadOptions := make(types.FieldOptions, len(squads))
for i, squad := range squads {
squadsByID[squad.ID] = squad.Name
squadOptions[i] = types.FieldOption{
Text: squad.Name,
Value: strconv.Itoa(squad.ID),
}
}
info := userTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
info.AddField("ID", "id", db.Int).
FieldSortable()
info.AddField("Squads", "squad_ids", db.Varchar).
FieldDisplay(func(model types.FieldModel) interface{} {
values := model.Row["squad_ids"].([]interface{})
labels := template.HTML("")
labelTpl := label(ctx).SetType("success")
labelValues := make([]string, len(values))
for i, squadID := range values {
labelValues[i] = squadsByID[int(squadID.(float64))]
}
for key, label := range labelValues {
if key == len(labelValues)-1 {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
} else {
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
}
}
return labels
})
info.AddField("Username", "username", db.Varchar).
FieldFilterable().
FieldSortable()
info.AddField("Type", "type", db.Varchar).
FieldFilterable(
types.FilterType{
FormType: form.SelectSingle,
Options: types.FieldOptions{
{Text: "Hysteria", Value: "hysteria"},
{Text: "Hysteria2", Value: "hysteria2"},
{Text: "Trojan", Value: "trojan"},
{Text: "TUIC", Value: "tuic"},
{Text: "VLESS", Value: "vless"},
{Text: "VMess", Value: "vmess"},
},
},
).
FieldSortable()
info.AddField("Inbound", "inbound", db.Varchar).FieldFilterable().
FieldSortable()
info.AddField("Created at", "created_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.AddField("Updated at", "updated_at", db.Datetime).
FieldDisplay(func(model types.FieldModel) interface{} {
t, err := time.Parse(time.RFC3339, model.Value)
if err != nil {
return model.Value
}
return t.Format("2006-01-02 15:04:05")
}).
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
FieldSortable()
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
filters := make(map[string][]string, len(param.Fields))
listFilters := make(map[string][]string, len(param.Fields)+2)
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
listFilters["limit"] = []string{param.PageSize}
for key, values := range param.Fields {
if key == "__pk" {
key = "pk"
} else {
if strings.HasPrefix(key, "__") {
continue
}
key = strings.TrimSuffix(key, "__goadmin")
}
filters[key] = values
listFilters[key] = values
}
if param.SortField != "" {
if param.SortType == "asc" {
listFilters["sort_asc"] = []string{param.SortField}
} else {
listFilters["sort_desc"] = []string{param.SortField}
}
}
users, err := manager.GetUsers(listFilters)
if err != nil {
logger.Error(err)
return nil, 0
}
count, err := manager.GetUsersCount(filters)
if err != nil {
logger.Error(err)
return nil, 0
}
result := make([]map[string]interface{}, 0, len(users))
for _, user := range users {
var data map[string]interface{}
rawData, _ := json.Marshal(user)
json.Unmarshal(rawData, &data)
result = append(result, data)
}
return result, count
})
info.SetDeleteFn(func(ids []string) error {
for _, id := range ids {
value, err := strconv.Atoi(id)
if err != nil {
return err
}
if _, err := manager.DeleteUser(value); err != nil {
return err
}
}
return nil
})
info.SetTable("users").SetTitle("Users").SetDescription("Users")
formList := userTable.GetForm()
formList.AddField("ID", "id", db.Int, form.Default).
FieldNotAllowEdit().
FieldNotAllowAdd()
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
FieldMust().
FieldOptions(squadOptions).
FieldDisableWhenUpdate()
formList.AddField("Username", "username", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate()
formList.AddField("Type", "type", db.Varchar, form.SelectSingle).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate().
FieldOptions(types.FieldOptions{
{Text: "Hysteria", Value: "hysteria"},
{Text: "Hysteria2", Value: "hysteria2"},
{Text: "Trojan", Value: "trojan"},
{Text: "TUIC", Value: "tuic"},
{Text: "VLESS", Value: "vless"},
{Text: "VMess", Value: "vmess"},
}).
FieldOnChooseOptionsHide([]string{""}, "inbound").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic"}, "uuid").
FieldOnChooseOptionsHide([]string{"", "vless", "vmess"}, "password").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vmess"}, "flow").
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vless"}, "alter_id")
formList.AddField("Inbound", "inbound", db.Varchar, form.Text).
FieldMust().
FieldDisplayButCanNotEditWhenUpdate().
FieldOptionInitFn(func(val types.FieldModel) types.FieldOptions {
return types.FieldOptions{
{Value: val.Value, Text: val.Value, Selected: true},
}
})
formList.AddField("UUID", "uuid", db.Varchar, form.Text)
formList.AddField("Password", "password", db.Varchar, form.Text)
formList.AddField("Flow", "flow", db.Varchar, form.SelectSingle).
FieldOptions(types.FieldOptions{
{Text: "xtls-rprx-vision", Value: "xtls-rprx-vision"},
})
formList.AddField("Alter ID", "alter_id", db.Varchar, form.Number).
FieldDefault("0")
formList.SetInsertFn(func(values mForm.Values) (err error) {
squadIDs := make([]int, len(values["squad_ids[]"]))
for i, rawSquadID := range values["squad_ids[]"] {
squadID, err := strconv.Atoi(rawSquadID)
if err != nil {
return err
}
squadIDs[i] = squadID
}
var alterId int
if value := values.Get("alter_id"); value != "" {
alterId, err = strconv.Atoi(value)
if err != nil {
return err
}
}
_, err = manager.CreateUser(CM.UserCreate{
SquadIDs: squadIDs,
Username: values.Get("username"),
Type: values.Get("type"),
Inbound: values.Get("inbound"),
UUID: values.Get("uuid"),
Password: values.Get("password"),
Flow: values.Get("flow"),
AlterID: alterId,
})
if err != nil {
if ve, ok := err.(validator.ValidationErrors); ok {
var errors []string
for _, e := range ve {
switch e.Tag() {
case "required":
errors = append(errors, e.StructField()+": required field missing")
case "uuid4":
errors = append(errors, e.StructField()+": invalid UUID")
default:
errors = append(errors, e.StructField()+": invalid request")
}
}
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
}
}
return
})
formList.SetUpdateFn(func(values mForm.Values) (err error) {
id, err := strconv.Atoi(values.Get("id"))
if err != nil {
return err
}
var alterId int
if value := values.Get("alter_id"); value != "" {
alterId, err = strconv.Atoi(value)
if err != nil {
return err
}
}
_, err = manager.UpdateUser(id, CM.UserUpdate{
UUID: values.Get("uuid"),
Password: values.Get("password"),
Flow: values.Get("flow"),
AlterID: alterId,
})
return
})
formList.SetTable("users").SetTitle("Users").SetDescription("Users")
return
}
}