Compare commits

...

1 Commits

Author SHA1 Message Date
8bc4f4fe04 Tui 模块开发完成,数据正常写入数据库 2026-03-06 16:33:08 +08:00
7 changed files with 674 additions and 535 deletions

View File

@@ -3,6 +3,7 @@ package database
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"strings"
"sync" "sync"
"sunhpc/pkg/config" "sunhpc/pkg/config"
@@ -22,22 +23,20 @@ var (
dbErr error dbErr error
) )
// =========================================================
// 封装数据库函数使用Go实现 // 封装数据库函数使用Go实现
// =========================================================
// MapCategory - 根据类别名称查ID // MapCategory - 根据类别名称查ID
// 查询方式: globalID, err := db.MapCategory(conn, "global") // 查询方式: globalID, err := db.MapCategory(conn, "global")
func MapCategory(conn *sql.DB, catname string) (int, error) { func MapCategory(conn *sql.DB, catname string) (int, error) {
var id int var id int
query := "select id from categories where name = ?" query := "select id from categories where name = ?"
logger.Debugf("查询SQL: %s", query) fullSQL := ReplaceSQLQuery(query, catname)
logger.Debugf("查询类别ID: %s", catname)
err := conn.QueryRow(query, catname).Scan(&id) err := conn.QueryRow(query, catname).Scan(&id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
logger.Debugf("未找到类别 %s, 返回ID=0", catname) logger.Debugf("未找到类别 %s, 返回ID=0", catname)
return 0, nil // 无匹配返回0 return 0, nil // 无匹配返回0
} }
logger.Debugf("查询到类别 %s, ID=%d", catname, id) logger.Debugf("查询语句: %s , CatName=%s, ID=%d", fullSQL, catname, id)
return id, nil return id, nil
} }
@@ -49,14 +48,15 @@ func MapCategoryIndex(conn *sql.DB, catindexName, categoryIndex string) (int, er
select index_id from vmapCategoryIndex select index_id from vmapCategoryIndex
where categoryName = ? and categoryIndex = ? where categoryName = ? and categoryIndex = ?
` `
logger.Debugf("查询SQL: %s", query) fullSQL := ReplaceSQLQuery(query, catindexName, categoryIndex)
logger.Debugf("查询索引ID: %s, 类别: %s", catindexName, categoryIndex)
err := conn.QueryRow(query, catindexName, categoryIndex).Scan(&id) err := conn.QueryRow(query, catindexName, categoryIndex).Scan(&id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
logger.Debugf("未找到索引 %s, 返回ID=0", catindexName) logger.Debugf("未找到索引 %s, 返回ID=0", catindexName)
return 0, nil // 无匹配返回0 return 0, nil // 无匹配返回0
} }
logger.Debugf("查询到索引 %s, ID=%d", catindexName, id) logger.Debugf("查询语句: %s , CatIndexName=%s, CategoryIndex=%s, ID=%d",
fullSQL, catindexName, categoryIndex, id)
return id, nil return id, nil
} }
@@ -165,6 +165,13 @@ func GetDB() (*sql.DB, error) {
return return
} }
var version string
err = sqlDB.QueryRow("select sqlite_version()").Scan(&version)
if err != nil {
version = "unknown"
}
logger.Debugf("数据库版本: %s", version)
logger.Debug("数据库连接成功") logger.Debug("数据库连接成功")
dbInstance = sqlDB dbInstance = sqlDB
}) })
@@ -178,10 +185,44 @@ func GetDB() (*sql.DB, error) {
func InitTables(db *sql.DB, force bool) error { func InitTables(db *sql.DB, force bool) error {
// 临时关闭外键约束(解决外键依赖删除报错问题)
_, err := db.Exec("PRAGMA foreign_keys = OFF;")
if err != nil {
logger.Errorf("关闭外键约束失败: %v", err)
return err
}
defer func() {
// 延迟恢复外键约束(确保在函数退出时恢复)
_, err := db.Exec("PRAGMA foreign_keys = ON;")
if err != nil {
logger.Errorf("恢复外键约束失败: %v", err)
}
}()
// ✅ 调用 schema.go 中的函数 // ✅ 调用 schema.go 中的函数
//for _, ddl := range CreateTableStatements() { for name, ddl := range BaseTables() {
for _, ddl := range BaseTables() { // 删除表或者试图(如果存在)
logger.Debugf("执行: %s", ddl) logger.Debugf("执行删除 - %s", name)
// 先尝试作为表进行删除
query := fmt.Sprintf("DROP TABLE IF EXISTS %s;", name)
logger.Debugf("执行语句: %s", query)
_, err := db.Exec(query)
if err != nil {
// 如果作为表删除失败,尝试作为试图删除
logger.Debugf("删除表失败: %v", err)
query = fmt.Sprintf("DROP VIEW IF EXISTS %s;", name)
logger.Debugf("执行语句: %s", query)
_, err = db.Exec(query)
if err != nil {
return fmt.Errorf("删除失败: %w", err)
}
}
logger.Debugf("执行图表 - %s", name)
logger.Debugf("执行语句: %s", ddl)
if _, err := db.Exec(ddl); err != nil { if _, err := db.Exec(ddl); err != nil {
return fmt.Errorf("数据表创建失败: %w", err) return fmt.Errorf("数据表创建失败: %w", err)
} }
@@ -291,28 +332,36 @@ func ExecWithTransaction(ddl []string) error {
return err return err
} }
var finished bool
// 延迟处理:如果函数异常,回滚事务 // 延迟处理:如果函数异常,回滚事务
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
// 捕获 panic 并回滚事务 if !finished {
tx.Rollback() // 捕获 panic 并回滚事务
logger.Errorf("事务执行中发生 panic: %v", r) tx.Rollback()
logger.Errorf("事务执行中发生 panic: %v", r)
}
panic(r)
} }
}() }()
// 遍历执行 DDL 语句 // 遍历执行 DDL 语句
for idx, sql := range ddl { for idx, sql := range ddl {
logger.Debugf("执行 DDL 语句 %d: %s", idx+1, sql) logger.Debugf("执行 DDL 语句 %d: %s", idx+1, sql)
_, err = tx.Exec(sql) _, err = tx.Exec(sql)
if err != nil { if err != nil {
// 执行失败时,回滚事务 // 执行失败时,回滚事务
rollbackErr := tx.Rollback() rollbackErr := tx.Rollback()
finished = true // 标记事务已完成
if rollbackErr != nil { if rollbackErr != nil {
logger.Errorf("执行失败: 回滚失败: %v (原错误: %v, SQL: %s)", rollbackErr, err, sql) logger.Errorf("执行失败: 回滚失败: %v (原错误: %v, SQL: %s)", rollbackErr, err, sql)
} else { } else {
logger.Errorf("执行失败: 回滚事务: %v, SQL: %s", err, sql) logger.Errorf("执行失败: 回滚事务: %v, SQL: %s", err, sql)
} }
logger.Errorf("执行 %d 条, 失败: %w (SQL: %s)", idx+1, err, sql) logger.Errorf("执行 %d 条, 失败: %w (SQL: %s)", idx+1, err, sql)
return fmt.Errorf("执行 %d 条, 失败: %w (SQL: %s)", idx+1, err, sql)
} }
} }
@@ -323,6 +372,21 @@ func ExecWithTransaction(ddl []string) error {
return err return err
} }
finished = true // 标记事务已完成
logger.Debugf("成功执行 %d 条 SQL 语句, 事务已提交.", len(ddl)) logger.Debugf("成功执行 %d 条 SQL 语句, 事务已提交.", len(ddl))
return nil return nil
} }
func ReplaceSQLQuery(query string, args ...interface{}) string {
for _, arg := range args {
switch v := arg.(type) {
case string:
query = strings.Replace(query, "?", fmt.Sprintf("'%s'", v), 1)
case int, int64, float64:
query = strings.Replace(query, "?", fmt.Sprintf("%v", v), 1)
default:
query = strings.Replace(query, "?", fmt.Sprintf("%v", v), 1)
}
}
return strings.TrimSpace(strings.ReplaceAll(query, "\n", " "))
}

View File

@@ -4,473 +4,412 @@ package database
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"sunhpc/pkg/logger"
) )
func BaseTables() []string { func BaseTables() map[string]string {
datalist := []string{} return map[string]string{
"appliances": `
Appliances := ` CREATE TABLE IF NOT EXISTS appliances (
CREATE TABLE IF NOT EXISTS appliances ( ID integer primary key autoincrement,
ID integer primary key autoincrement, Name varchar(32) not null default '',
Name varchar(32) not null default '', Graph varchar(64) not null default 'default',
Graph varchar(64) not null default 'default', Node varchar(64) not null default '',
Node varchar(64) not null default '', OS varchar(64) not null default 'linux'
OS varchar(64) not null default 'linux' );
); `,
` "memberships": `
datalist = append(datalist, Appliances) CREATE TABLE IF NOT EXISTS memberships (
ID integer primary key autoincrement,
Memberships := ` Name varchar(64) not null default '',
CREATE TABLE IF NOT EXISTS memberships ( Appliance integer(11) default '0',
ID integer primary key autoincrement, Distribution integer(11) default '1',
Name varchar(64) not null default '', Public varchar(64) not null default 'no'
Appliance integer(11) default '0', );
Distribution integer(11) default '1', `,
Public varchar(64) not null default 'no' "categories": `
); CREATE TABLE IF NOT EXISTS categories (
` ID integer primary key autoincrement,
datalist = append(datalist, Memberships) Name varchar(64) not null unique default '0',
Description varchar(255) default null,
Categories := ` UNIQUE(Name)
CREATE TABLE IF NOT EXISTS categories ( );
ID integer primary key autoincrement, `,
Name varchar(64) not null unique default '0', "catindex": `
Description varchar(255) default null, CREATE TABLE IF NOT EXISTS catindex (
UNIQUE(Name) ID integer primary key autoincrement,
); Name varchar(64) not null unique default '0',
` Category integer not null,
datalist = append(datalist, Categories) Foreign key(Category) references categories(ID) on delete cascade
);
Catindex := ` `,
CREATE TABLE IF NOT EXISTS catindex ( "resolvechain": `
ID integer primary key autoincrement, CREATE TABLE IF NOT EXISTS resolvechain (
Name varchar(64) not null unique default '0', ID integer primary key autoincrement,
Category integer not null, Name varchar(64) not null default '0',
Foreign key(Category) references categories(ID) on delete cascade Category integer(11) not null,
); Precedence integer(11) not null default '10',
` UNIQUE(Name, Category)
datalist = append(datalist, Catindex) Foreign key(Category) references categories(ID) on delete cascade
);
Resolvechain := ` `,
CREATE TABLE IF NOT EXISTS resolvechain ( "nodes": `
ID integer primary key autoincrement, CREATE TABLE IF NOT EXISTS nodes (
Name varchar(64) not null default '0', ID integer primary key autoincrement,
Category integer(11) not null, Name varchar default null,
Precedence integer(11) not null default '10', Membership integer default '2',
UNIQUE(Name, Category) CPUs integer not null default '1',
Foreign key(Category) references categories(ID) on delete cascade Rack varchar default null,
); Rank integer default null,
` Arch varchar default null,
datalist = append(datalist, Resolvechain) OS varchar default null,
RunAction varchar(64) default 'os',
Nodes := ` InstallAction varchar(64) default 'install'
CREATE TABLE IF NOT EXISTS nodes ( );
ID integer primary key autoincrement, create index if not exists idx_nodes_name on nodes(Name);
Name varchar(128) default null, `,
Membership integer(11) default '2', "aliases": `
CPUs integer(11) not null default '1', CREATE TABLE IF NOT EXISTS aliases (
Rack varchar(11) default null, ID integer primary key autoincrement,
Rank integer(11) default null, Node integer not null default '0',
Arch varchar(32) default null, Name varchar default null,
OS varchar(64) not null default 'linux', Foreign key(Node) references nodes(ID) on delete cascade
RunAction varchar(64) default 'os', );
InstallAction varchar(64) default 'install' create index if not exists idx_aliases_name on aliases(Name);
); `,
create index if not exists idx_nodes_name on nodes(Name); "networks": `
` CREATE TABLE IF NOT EXISTS networks (
datalist = append(datalist, Nodes) ID integer primary key autoincrement,
Node integer not null default '0',
Aliases := ` MAC varchar default null,
CREATE TABLE IF NOT EXISTS aliases ( IP varchar default null,
ID integer primary key autoincrement, Name varchar default null,
Node integer(15) not null default '0', Device varchar default null,
Name varchar(32) default null, Subnet integer default null,
Foreign key(Node) references nodes(ID) on delete cascade Module varchar default null,
); VlanID integer default null,
` Options varchar default null,
datalist = append(datalist, Aliases) Channel varchar default null,
Foreign key(Node) references nodes(ID) on delete cascade,
Networks := ` Foreign key(Subnet) references subnets(ID) on delete cascade
CREATE TABLE IF NOT EXISTS networks ( );
ID integer primary key autoincrement, create index if not exists idx_networks_name on networks(Name);
Node integer(11) default null, `,
MAC varchar(64) default null, "globalroutes": `
IP varchar(64) default null, CREATE TABLE IF NOT EXISTS globalroutes (
Name varchar(128) default null, Network varchar(32) not null default '',
Device varchar(32) default null, Netmask varchar(32) not null default '',
Subnet integer(11) default null, Gateway varchar(32) not null default '',
Module varchar(128) default null, Subnet integer default null,
VlanID integer(11) default null, Primary key(Network, Netmask)
Options varchar(128) default null, Foreign key(Subnet) references subnets(ID) on delete cascade
Channel varchar(128) default null, );
Foreign key(Node) references nodes(ID) on delete cascade, `,
Foreign key(Subnet) references subnets(ID) on delete cascade "osroutes": `
); CREATE TABLE IF NOT EXISTS osroutes (
` OS varchar(64) not null default 'linux',
datalist = append(datalist, Networks) Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
GlobalRoutes := ` Gateway varchar(32) not null default '',
CREATE TABLE IF NOT EXISTS globalroutes ( Subnet integer default null,
Network varchar(32) not null default '', Primary key(OS, Network, Netmask)
Netmask varchar(32) not null default '', Foreign key(Subnet) references subnets(ID) on delete cascade
Gateway varchar(32) not null default '', );
Subnet integer(11) default null, `,
Primary key(Network, Netmask) "applianceroutes": `
Foreign key(Subnet) references subnets(ID) on delete cascade CREATE TABLE IF NOT EXISTS applianceroutes (
); Appliance varchar(11) not null default '0',
` Network varchar(32) not null default '',
datalist = append(datalist, GlobalRoutes) Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
OSRoutes := ` Subnet integer default null,
CREATE TABLE IF NOT EXISTS osroutes ( Primary key(Appliance, Network, Netmask)
OS varchar(64) not null default 'linux', Foreign key(Subnet) references subnets(ID) on delete cascade
Network varchar(32) not null default '', );
Netmask varchar(32) not null default '', `,
Gateway varchar(32) not null default '', "noderoutes": `
Subnet integer(11) default null, CREATE TABLE IF NOT EXISTS noderoutes (
Primary key(OS, Network, Netmask) Node varchar(11) not null default '0',
Foreign key(Subnet) references subnets(ID) on delete cascade Network varchar(32) not null default '',
); Netmask varchar(32) not null default '',
` Gateway varchar(32) not null default '',
datalist = append(datalist, OSRoutes) Subnet integer default null,
Primary key(Node, Network, Netmask)
ApplianceRoutes := ` Foreign key(Subnet) references subnets(ID) on delete cascade
CREATE TABLE IF NOT EXISTS applianceroutes ( );
Appliance varchar(11) not null default '0', `,
Network varchar(32) not null default '', "subnets": `
Netmask varchar(32) not null default '', CREATE TABLE IF NOT EXISTS subnets (
Gateway varchar(32) not null default '', ID integer primary key autoincrement,
Subnet integer(11) default null, name varchar(32) unique not null,
Primary key(Appliance, Network, Netmask) dnszone varchar(64) unique not null,
Foreign key(Subnet) references subnets(ID) on delete cascade subnet varchar(32) default null,
); netmask varchar(32) default null,
` mtu integer(11) default '1500',
datalist = append(datalist, ApplianceRoutes) servedns boolean default false
);
NodeRoutes := ` `,
CREATE TABLE IF NOT EXISTS noderoutes ( "publickeys": `
Node varchar(11) not null default '0', CREATE TABLE IF NOT EXISTS publickeys (
Network varchar(32) not null default '', ID integer primary key autoincrement,
Netmask varchar(32) not null default '', Node integer(11) not null default '0',
Gateway varchar(32) not null default '', Public_Key varchar(8192) default null,
Subnet integer(11) default null, Description varchar(8192) default null,
Primary key(Node, Network, Netmask) Foreign key(Node) references nodes(ID) on delete cascade
Foreign key(Subnet) references subnets(ID) on delete cascade );
); `,
` "secglobal": `
datalist = append(datalist, NodeRoutes) CREATE TABLE IF NOT EXISTS secglobal (
Attr varchar(128) default null,
Subnets := ` Value text,
CREATE TABLE IF NOT EXISTS subnets ( Enc varchar(128) default null,
ID integer primary key autoincrement, Primary key(Attr)
name varchar(32) unique not null, );
dnszone varchar(64) unique not null, `,
subnet varchar(32) default null, "secnodes": `
netmask varchar(32) default null, CREATE TABLE IF NOT EXISTS secnodes (
mtu integer(11) default '1500', Attr varchar(128) default null,
servedns boolean default false Enc varchar(128) default null,
); Value text,
` Node integer(15) not null default '0',
datalist = append(datalist, Subnets) Primary key(Attr, Node)
);
PublicKeys := ` `,
CREATE TABLE IF NOT EXISTS publickeys ( "attributes": `
ID integer primary key autoincrement, CREATE TABLE IF NOT EXISTS attributes (
Node integer(11) not null default '0', ID integer primary key autoincrement,
Public_Key varchar(8192) default null, Attr varchar(128) not null,
Description varchar(8192) default null, Value text,
Foreign key(Node) references nodes(ID) on delete cascade Shadow text,
); Category integer(11) not null,
` Catindex integer(11) not null,
datalist = append(datalist, PublicKeys) UNIQUE(Attr, Category, Catindex),
Foreign key(Catindex) references catindex(ID) on delete cascade
SecGlobal := ` );
CREATE TABLE IF NOT EXISTS secglobal ( `,
Attr varchar(128) default null, "partitions": `
Value text, CREATE TABLE IF NOT EXISTS partitions (
Enc varchar(128) default null, ID integer primary key autoincrement,
Primary key(Attr) Node integer(15) not null default '0',
); Device varchar(128) not null default '',
` MountPoint varchar(128) not null default '',
datalist = append(datalist, SecGlobal) SectorStart varchar(128) not null default '',
PartitionSize varchar(128) not null default '',
SecNodes := ` FsType varchar(128) not null default '',
CREATE TABLE IF NOT EXISTS secnodes ( PartitionFlags varchar(128) not null default '',
Attr varchar(128) default null, FormatFlags varchar(128) not null default ''
Enc varchar(128) default null, );
Value text, `,
Node integer(15) not null default '0', "firewalls": `
Primary key(Attr, Node) CREATE TABLE IF NOT EXISTS firewalls (
); ID integer primary key autoincrement,
` Rulename varchar(128) not null,
datalist = append(datalist, SecNodes) Rulesrc varchar(256) not null default 'custom',
InSubnet int(11),
Attributes := ` OutSubnet int(11),
CREATE TABLE IF NOT EXISTS attributes ( Service varchar(256),
ID integer primary key autoincrement, Protocol varchar(256),
Attr varchar(128) not null, Action varchar(256),
Value text, Chain varchar(256),
Shadow text, Flags varchar(256),
Category integer(11) not null, Comment varchar(256),
Catindex integer(11) not null, Category integer(11) not null,
UNIQUE(Attr, Category, Catindex), Catindex integer(11) not null,
Foreign key(Catindex) references catindex(ID) on delete cascade Check(rulesrc IN ('system', 'custom'))
); UNIQUE(Rulename, Category, Catindex),
` Foreign key(Catindex) references catindex(ID) on delete cascade
datalist = append(datalist, Attributes) );
`,
Partitions := ` "rolls": `
CREATE TABLE IF NOT EXISTS partitions ( CREATE TABLE IF NOT EXISTS rolls (
ID integer primary key autoincrement, ID integer primary key autoincrement,
Node integer(15) not null default '0', Name varchar(128) not null default '',
Device varchar(128) not null default '', Version varchar(32) not null default '',
MountPoint varchar(128) not null default '', Arch varchar(32) not null default '',
SectorStart varchar(128) not null default '', OS varchar(64) not null default 'linux',
PartitionSize varchar(128) not null default '', Enabled varchar(3) not null default 'yes',
FsType varchar(128) not null default '', Check(Enabled IN ('yes', 'no'))
PartitionFlags varchar(128) not null default '', Check(OS IN ('linux', 'other'))
FormatFlags varchar(128) not null default '' );
); `,
` "noderolls": `
datalist = append(datalist, Partitions) CREATE TABLE IF NOT EXISTS noderolls (
Node varchar(11) not null default '0',
Firewalls := ` RollID varchar(11) not null,
CREATE TABLE IF NOT EXISTS firewalls ( Primary key(Node, RollID)
ID integer primary key autoincrement, );
Rulename varchar(128) not null, `,
Rulesrc varchar(256) not null default 'custom', "bootactions": `
InSubnet int(11), CREATE TABLE IF NOT EXISTS bootactions (
OutSubnet int(11), ID integer primary key autoincrement,
Service varchar(256), Action varchar(256) default null,
Protocol varchar(256), Kernel varchar(256) default null,
Action varchar(256), Ramdisk varchar(256) default null,
Chain varchar(256), Args varchar(1024) default null
Flags varchar(256), );
Comment varchar(256), `,
Category integer(11) not null, "bootflags": `
Catindex integer(11) not null, CREATE TABLE IF NOT EXISTS bootflags (
Check(rulesrc IN ('system', 'custom')) ID integer primary key autoincrement,
UNIQUE(Rulename, Category, Catindex), Node integer(11) not null default '0',
Foreign key(Catindex) references catindex(ID) on delete cascade Flags varchar(256) default null
); );
` `,
datalist = append(datalist, Firewalls) "distributions": `
CREATE TABLE IF NOT EXISTS distributions (
Rolls := ` ID integer primary key autoincrement,
CREATE TABLE IF NOT EXISTS rolls ( Name varchar(32) not null default '',
ID integer primary key autoincrement, OS varchar(32) default '',
Name varchar(128) not null default '', Release varchar(32) default ''
Version varchar(32) not null default '', );
Arch varchar(32) not null default '', `,
OS varchar(64) not null default 'linux', "vnet": `
Enabled varchar(3) not null default 'yes', DROP VIEW IF EXISTS vnet;
Check(Enabled IN ('yes', 'no')) CREATE VIEW vnet AS
Check(OS IN ('linux', 'other')) SELECT
); n.name AS nodename, /* 查询nodes表中name字段,将字段改名为nodename */
` m.name AS membership,
datalist = append(datalist, Rolls) a.name AS appliance,
n.rack, n.rank, /* 查询nodes表中rack和rank字段,使用原始字段名 */
NodeRolls := ` s.name AS subnet,
CREATE TABLE IF NOT EXISTS noderolls ( nt.ip, nt.device, nt.module,
Node varchar(11) not null default '0', nt.name AS hostname,
RollID varchar(11) not null, s.dnszone AS domainname,
Primary key(Node, RollID) s.netmask, s.mtu
); FROM
` nodes n /* 主表: 先查询nodes表,别名n */
datalist = append(datalist, NodeRolls) inner join memberships m on n.membership=m.id /* 连接memberships表,on只保留满足条件的行 */
inner join appliances a on m.appliance=a.id /* 连接appliances表,on只保留满足条件的行 */
Bootactions := ` inner join networks nt on n.id=nt.node /* 连接networks表,on只保留满足条件的行 */
CREATE TABLE IF NOT EXISTS bootactions ( inner join subnets s on nt.subnet=s.id /* 连接subnets表,on只保留满足条件的行 */
ID integer primary key autoincrement, ;
Action varchar(256) default null, `,
Kernel varchar(256) default null, "hostselections": `
Ramdisk varchar(256) default null, DROP VIEW IF EXISTS hostselections;
Args varchar(1024) default null CREATE VIEW hostselections AS
); SELECT
` n.name AS host,
datalist = append(datalist, Bootactions) c.id as category,
ci.id as selection
BootFlags := ` FROM
CREATE TABLE IF NOT EXISTS bootflags ( nodes n
ID integer primary key autoincrement, inner join memberships m on n.membership=m.id -- 节点表关联所属分组
Node integer(11) not null default '0', inner join appliances a on m.appliance=a.id -- 分组关联所属应用角色
Flags varchar(256) default null inner join categories c on
); -- 匹配4类分层配置的category(全局/OS/应用/主机)
` c.name in ('global', 'os', 'appliance', 'host')
datalist = append(datalist, BootFlags) inner join catindex ci on
-- 核心匹配逻辑: category和catindex的name字段一一对应
Distributions := ` (c.name = 'global' and ci.name = 'global') or
CREATE TABLE IF NOT EXISTS distributions ( (c.name = 'os' and ci.name = n.os) or
ID integer primary key autoincrement, (c.name = 'appliance' and ci.name = a.name) or
Name varchar(32) not null default '', (c.name = 'host' and ci.name = n.name)
OS varchar(32) default '', ;
Release varchar(32) default '' `,
); "vcatindex": `
` -- 视图vcatindex: 类别索引可读试图
datalist = append(datalist, Distributions) DROP VIEW IF EXISTS vcatindex;
CREATE VIEW vcatindex AS
View_vnet := ` SELECT
DROP VIEW IF EXISTS vnet; c.id AS ID,
CREATE VIEW vnet AS cat.Name AS Category,
SELECT ci.Name AS catindex
n.name AS nodename, /* 查询nodes表中name字段,将字段改名为nodename */ FROM
m.name AS membership, categories cat
a.name AS appliance, inner join catindex ci on ci.category=cat.id
n.rack, n.rank, /* 查询nodes表中rack和rank字段,使用原始字段名 */ ;
s.name AS subnet, `,
nt.ip, nt.device, nt.module, "vresolvechain": `
nt.name AS hostname, -- 视图vresolvechain: 解析链可读试图
s.dnszone AS domainname, DROP VIEW IF EXISTS vresolvechain;
s.netmask, s.mtu CREATE VIEW vresolvechain AS
FROM SELECT
nodes n /* 主表: 先查询nodes表,别名n */ r.name AS chain,
inner join memberships m on n.membership=m.id /* 连接memberships表,on只保留满足条件的行 */ cat.name AS category,
inner join appliances a on m.appliance=a.id /* 连接appliances表,on只保留满足条件的行 */ precedence
inner join networks nt on n.id=nt.node /* 连接networks表,on只保留满足条件的行 */ FROM
inner join subnets s on nt.subnet=s.id /* 连接subnets表,on只保留满足条件的行 */ resolvechain r
; inner join categories cat on r.category=cat.id
` order by chain, precedence
datalist = append(datalist, View_vnet) ;
`,
View_hostselections := ` "vattributes": `
DROP VIEW IF EXISTS hostselections; -- 视图vattributes: 属性可读试图
CREATE VIEW hostselections AS DROP VIEW IF EXISTS vattributes;
SELECT CREATE VIEW vattributes AS
n.name AS host, SELECT
c.id as category, a.id,
ci.id as selection attr,
FROM value,
nodes n shadow,
inner join memberships m on n.membership=m.id -- 节点表关联所属分组 cat.name AS category,
inner join appliances a on m.appliance=a.id -- 分组关联所属应用角色 ci.name AS catindex
inner join categories c on FROM
-- 匹配4类分层配置的category(全局/OS/应用/主机) attributes a
c.name in ('global', 'os', 'appliance', 'host') inner join catindex ci on a.catindex=ci.id
inner join catindex ci on inner join categories cat on a.category=cat.id
-- 核心匹配逻辑: category和catindex的name字段一一对应 order by attr, catindex, category
(c.name = 'global' and ci.name = 'global') or ;
(c.name = 'os' and ci.name = n.os) or `,
(c.name = 'appliance' and ci.name = a.name) or "vfirewalls": `
(c.name = 'host' and ci.name = n.name) -- 视图vfirewalls: 防火墙规则可读试图
; DROP VIEW IF EXISTS vfirewalls;
` CREATE VIEW vfirewalls AS
datalist = append(datalist, View_hostselections) SELECT
f.id,
View_vcatindex := ` f.Rulename,
-- 视图vcatindex: 类别索引可读试图 f.Rulesrc,
DROP VIEW IF EXISTS vcatindex; f.InSubnet,
CREATE VIEW vcatindex AS f.OutSubnet,
SELECT f.Service,
c.id AS ID, f.Protocol,
cat.Name AS Category, f.Action,
ci.Name AS catindex f.Chain,
FROM f.Flags,
categories cat f.Comment,
inner join catindex ci on ci.category=cat.id cat.name AS category,
; ci.name AS catindex
` FROM
datalist = append(datalist, View_vcatindex) firewalls f
inner join catindex ci on f.catindex=ci.id
View_vresolvechain := ` inner join categories cat on f.category=cat.id
-- 视图vresolvechain: 解析链可读试图 order by f.Rulename, catindex, category
DROP VIEW IF EXISTS vresolvechain; ;
CREATE VIEW vresolvechain AS `,
SELECT "vhostselections": `
r.name AS chain, -- 视图vhostselections: 主机选择可读试图
cat.name AS category, DROP VIEW IF EXISTS vhostselections;
precedence CREATE VIEW vhostselections AS
FROM SELECT
resolvechain r hs.host AS host,
inner join categories cat on r.category=cat.id cat.name AS category,
order by chain, precedence ci.name AS selection
; FROM
` hostselections hs
datalist = append(datalist, View_vresolvechain) inner join categories cat on hs.category=cat.id
inner join catindex ci on hs.selection=ci.id
View_vattributes := ` order by host, category, selection
-- 视图vattributes: 属性可读试图 ;
DROP VIEW IF EXISTS vattributes; `,
CREATE VIEW vattributes AS "vmapcategoryindex": `
SELECT -- 视图vmapcategoryindex: 类别索引映射可读试图
a.id, DROP VIEW IF EXISTS vmapcategoryindex;
attr, CREATE VIEW vmapCategoryIndex AS
value, SELECT
shadow, cat.name AS categoryName,
cat.name AS category, ci.name AS categoryIndex,
ci.name AS catindex ci.ID AS index_ID
FROM FROM
attributes a cateindex ci
inner join catindex ci on a.catindex=ci.id inner join categories cat on ci.category=cat.id
inner join categories cat on a.category=cat.id ;
order by attr, catindex, category `,
; }
`
datalist = append(datalist, View_vattributes)
View_vfirewalls := `
-- 视图vfirewalls: 防火墙规则可读试图
DROP VIEW IF EXISTS vfirewalls;
CREATE VIEW vfirewalls AS
SELECT
f.id,
f.Rulename,
f.Rulesrc,
f.InSubnet,
f.OutSubnet,
f.Service,
f.Protocol,
f.Action,
f.Chain,
f.Flags,
f.Comment,
cat.name AS category,
ci.name AS catindex
FROM
firewalls f
inner join catindex ci on f.catindex=ci.id
inner join categories cat on f.category=cat.id
order by f.Rulename, catindex, category
;
`
datalist = append(datalist, View_vfirewalls)
View_vhostselections := `
-- 视图vhostselections: 主机选择可读试图
DROP VIEW IF EXISTS vhostselections;
CREATE VIEW vhostselections AS
SELECT
hs.host AS host,
cat.name AS category,
ci.name AS selection
FROM
hostselections hs
inner join categories cat on hs.category=cat.id
inner join catindex ci on hs.selection=ci.id
order by host, category, selection
;
`
datalist = append(datalist, View_vhostselections)
View_vmapCategoryIndex := `
-- 视图vmapcategoryindex: 类别索引映射可读试图
DROP VIEW IF EXISTS vmapcategoryindex;
CREATE VIEW vmapCategoryIndex AS
SELECT
cat.name AS categoryName,
ci.name AS categoryIndex,
ci.ID AS index_ID
FROM
cateindex ci
inner join categories cat on ci.category=cat.id
;
`
datalist = append(datalist, View_vmapCategoryIndex)
return datalist
} }
func InitBaseData(conn *sql.DB) error { func InitBaseData(conn *sql.DB) error {
logger.Debug("初始化基础数据...")
// ========== 第一步:插入 categories 数据 ========== // ========== 第一步:插入 categories 数据 ==========
categoryData := []struct { categoryData := []struct {
Name string Name string
@@ -484,15 +423,26 @@ func InitBaseData(conn *sql.DB) error {
} }
// 批量插入 categories (忽略重复) // 批量插入 categories (忽略重复)
logger.Debug("插入 categories 数据...")
for _, cd := range categoryData { for _, cd := range categoryData {
query := ` query := `
insert or ignore into categories (Name, Description) insert or ignore into categories (Name, Description)
values (?, ?) values (?, ?)
` `
_, err := conn.Exec(query, cd.Name, cd.Description) fullSQL := ReplaceSQLQuery(query, cd.Name, cd.Description)
logger.Debugf("执行语句: %s", fullSQL)
// 执行 SQL 语句仍用占位符避免SQL注入
result, err := conn.Exec(query, cd.Name, cd.Description)
if err != nil { if err != nil {
return fmt.Errorf("error inserting category %s: %w", cd.Name, err) return fmt.Errorf("error inserting category %s: %w", cd.Name, err)
} }
id, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("error getting last insert ID: %w", err)
}
logger.Debugf("执行语句: %s, 插入ID: %d", fullSQL, id)
} }
// ========== 第二步:插入 catindex 数据 ========== // ========== 第二步:插入 catindex 数据 ==========
@@ -511,7 +461,9 @@ func InitBaseData(conn *sql.DB) error {
{"devel-server", "appliance"}, {"devel-server", "appliance"},
{"login", "appliance"}, {"login", "appliance"},
} }
// 批量插入 catindex (忽略重复) // 批量插入 catindex (忽略重复)
logger.Debug("插入 Catindex 数据...")
for _, ci := range catindexData { for _, ci := range catindexData {
// 动态获取类别ID (复用MapCategory函数) // 动态获取类别ID (复用MapCategory函数)
catID, err := MapCategory(conn, ci.Category) catID, err := MapCategory(conn, ci.Category)
@@ -527,10 +479,19 @@ func InitBaseData(conn *sql.DB) error {
insert or ignore into catindex (Name, Category) insert or ignore into catindex (Name, Category)
values (?, ?) values (?, ?)
` `
_, err = conn.Exec(query, ci.Name, catID) fullSQL := ReplaceSQLQuery(query, ci.Name, catID)
// 执行 SQL 语句仍用占位符避免SQL注入
result, err := conn.Exec(query, ci.Name, catID)
if err != nil { if err != nil {
return fmt.Errorf("error inserting catindex %s: %w", ci.Name, err) return fmt.Errorf("error inserting catindex %s: %w", ci.Name, err)
} }
id, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("error getting last insert ID: %w", err)
}
logger.Debugf("执行语句: %s, 插入ID: %d", fullSQL, id)
} }
// ========== 第三步:插入 resolvechain 数据 ========== // ========== 第三步:插入 resolvechain 数据 ==========
@@ -546,6 +507,7 @@ func InitBaseData(conn *sql.DB) error {
{"default", "host", 50}, {"default", "host", 50},
} }
// 批量插入 resolvechain (忽略重复) // 批量插入 resolvechain (忽略重复)
logger.Debugf("插入 resolvechain 数据...")
for _, rcd := range resolveChainData { for _, rcd := range resolveChainData {
// 动态获取类别ID (复用MapCategory函数) // 动态获取类别ID (复用MapCategory函数)
catID, err := MapCategory(conn, rcd.Category) catID, err := MapCategory(conn, rcd.Category)
@@ -561,10 +523,19 @@ func InitBaseData(conn *sql.DB) error {
insert or ignore into resolvechain (Name, Category, Precedence) insert or ignore into resolvechain (Name, Category, Precedence)
values (?, ?, ?) values (?, ?, ?)
` `
_, err = conn.Exec(query, rcd.Name, catID, rcd.Precedence) fullSQL := ReplaceSQLQuery(query, rcd.Name, catID, rcd.Precedence)
// 执行 SQL 语句仍用占位符避免SQL注入
result, err := conn.Exec(query, rcd.Name, catID, rcd.Precedence)
if err != nil { if err != nil {
return fmt.Errorf("error inserting resolvechain %s: %w", rcd.Name, err) return fmt.Errorf("error inserting resolvechain %s: %w", rcd.Name, err)
} }
id, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("error getting last insert ID: %w", err)
}
logger.Debugf("执行语句: %s, 插入ID: %d", fullSQL, id)
} }
return nil return nil

View File

@@ -80,6 +80,72 @@ type LogConfig struct {
ShowColor bool `mapstructure:"show_color" yaml:"show_color"` ShowColor bool `mapstructure:"show_color" yaml:"show_color"`
} }
type LevelFilterWriter struct {
writer io.Writer
maxLevel logrus.Level // 控制台: 只输出 <= 该级别
minLevel logrus.Level // 文件: 只输出 >= 该级别
isConsole bool // 是否是控制台输出
}
// Write 实现io.Writer接口核心过滤逻辑
func (f *LevelFilterWriter) Write(p []byte) (n int, err error) {
// 解析日志级别适配logrus默认格式和CustomFormatter
logLevel := parseLogLevelFromContent(p)
// 控制台:只输出 Info 及以下级别Trace/Debug/Info
if f.isConsole {
if logLevel <= f.maxLevel {
return f.writer.Write(p)
}
return len(p), nil // 过滤掉返回长度避免Writer报错
}
// 文件:只输出 Warn 及以上级别Warn/Error/Fatal/Panic
if logLevel >= f.minLevel {
return f.writer.Write(p)
}
return len(p), nil
}
// parseLogLevelFromContent 解析日志内容中的级别(兼容自定义格式)
func parseLogLevelFromContent(p []byte) logrus.Level {
content := string(p)
// 适配常见的级别关键字兼容你的CustomFormatter
switch {
case contains(content, "TRACE"):
return logrus.TraceLevel
case contains(content, "DEBUG"):
return logrus.DebugLevel
case contains(content, "INFO"):
return logrus.InfoLevel
case contains(content, "WARN") || contains(content, "WARNING"):
return logrus.WarnLevel
case contains(content, "ERROR"):
return logrus.ErrorLevel
case contains(content, "FATAL"):
return logrus.FatalLevel
case contains(content, "PANIC"):
return logrus.PanicLevel
default:
return logrus.InfoLevel // 解析失败默认Info级别
}
}
// contains 辅助函数:判断字符串是否包含子串
func contains(s, substr string) bool {
return len(s) >= len(substr) && indexOf(s, substr) != -1
}
// indexOf 简易字符串查找(避免依赖额外库)
func indexOf(s, substr string) int {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return i
}
}
return -1
}
// 默认配置 // 默认配置
var defaultConfig = LogConfig{ var defaultConfig = LogConfig{
Level: "info", Level: "info",
@@ -216,34 +282,7 @@ func Init(cfg LogConfig) {
// 1. 创建logrus实例 // 1. 创建logrus实例
logrusInst := logrus.New() logrusInst := logrus.New()
// 2. 配置输出(控制台 + 文件,可选) // 2. 配置日志级别(总开关,必须在输出配置前)
var outputs []io.Writer
outputs = append(outputs, os.Stdout) // 控制台输出
// 如果配置了日志文件,添加文件输出
if cfg.LogFile != "" {
// 确保日志目录存在
dir := filepath.Dir(cfg.LogFile)
if err := os.MkdirAll(dir, 0755); err != nil {
// 目录创建失败,只输出警告,不影响程序运行
logrusInst.Warnf("创建日志目录失败: %v,仅输出到控制台", err)
} else {
file, err := os.OpenFile(cfg.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
outputs = append(outputs, file)
} else {
logrusInst.Warnf("打开日志文件失败: %v,仅输出到控制台", err)
}
}
}
logrusInst.SetOutput(io.MultiWriter(outputs...))
// 3. 配置格式
logrusInst.SetFormatter(&CustomFormatter{
ShowColor: cfg.ShowColor,
})
// 4. 配置日志级别
lvl, err := logrus.ParseLevel(cfg.Level) lvl, err := logrus.ParseLevel(cfg.Level)
if err != nil { if err != nil {
lvl = logrus.InfoLevel // 解析失败默认Info级别 lvl = logrus.InfoLevel // 解析失败默认Info级别
@@ -257,6 +296,45 @@ func Init(cfg LogConfig) {
// 启用文件行号(必须开启否则getCallerInfo拿不到数据) // 启用文件行号(必须开启否则getCallerInfo拿不到数据)
logrusInst.SetReportCaller(true) logrusInst.SetReportCaller(true)
// 3. 配置输出(控制台 + 文件,可选)
var outputs []io.Writer
// 控制台输出: 只输出 Info 及以下级别
consoleWriter := &LevelFilterWriter{
writer: os.Stdout,
minLevel: logrus.InfoLevel,
isConsole: true,
}
outputs = append(outputs, consoleWriter)
// 如果配置了日志文件,添加文件输出
if cfg.LogFile != "" {
// 确保日志目录存在
dir := filepath.Dir(cfg.LogFile)
if err := os.MkdirAll(dir, 0755); err != nil {
// 目录创建失败,只输出警告,不影响程序运行
logrusInst.Warnf("创建日志目录失败: %v,仅输出到控制台", err)
} else {
file, err := os.OpenFile(cfg.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err == nil {
fileWriter := &LevelFilterWriter{
writer: file,
minLevel: logrus.WarnLevel,
isConsole: false,
}
outputs = append(outputs, fileWriter)
} else {
logrusInst.Warnf("打开日志文件失败: %v,仅输出到控制台", err)
}
}
}
logrusInst.SetOutput(io.MultiWriter(outputs...))
// 4. 配置格式
logrusInst.SetFormatter(&CustomFormatter{
ShowColor: cfg.ShowColor,
})
// 5. 赋值给全局默认实例 // 5. 赋值给全局默认实例
DefaultLogger = &logrusLogger{logrusInst} DefaultLogger = &logrusLogger{logrusInst}
}) })

View File

@@ -3,6 +3,7 @@ package utils
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt"
"os" "os"
"os/exec" "os/exec"
"time" "time"
@@ -32,6 +33,29 @@ func GetTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05") return time.Now().Format("2006-01-02 15:04:05")
} }
func OutputMaps(maps map[string]string) []string {
output := []string{}
maxLen := 0
for key := range maps {
if len(key) > maxLen {
maxLen = len(key)
}
}
// 使用动态宽度的格式化字符串输出
// %-*s 的含义
// %: 格式化开始
// -: 左对齐,默认是右对齐
// *: 表示宽度由后续参数指定(maxLen)
// s: 表示字符串类型
for key, value := range maps {
output = append(output, fmt.Sprintf("%-*s: %s", maxLen, key, value))
}
return output
}
// 定义短语 // 定义短语
const ( const (
NoAvailableNetworkInterfaces = "No available network interfaces" NoAvailableNetworkInterfaces = "No available network interfaces"

View File

@@ -201,7 +201,7 @@ func loadConfig() (*ConfigMapping, error) {
if _, err := toml.DecodeFile(cfgfile, configs); err != nil { if _, err := toml.DecodeFile(cfgfile, configs); err != nil {
if errors.Is(err, os.ErrNotExist) { if errors.Is(err, os.ErrNotExist) {
// 文件不存在,直接返回默认配置 // 文件不存在,直接返回默认配置
logger.Infof("Config file %s not exist, use default config", cfgfile) logger.Debugf("Config file %s not exist, use default config", cfgfile)
return configs, nil return configs, nil
} }
// 其他错误,返回错误 // 其他错误,返回错误
@@ -209,7 +209,7 @@ func loadConfig() (*ConfigMapping, error) {
return nil, err return nil, err
} }
logger.Infof("Load config file %s success", cfgfile) logger.Debugf("Load config file %s success", cfgfile)
return configs, nil return configs, nil
} }
@@ -310,7 +310,9 @@ func (m *model) saveConfig() error {
return err return err
} }
logger.Debugf("Result: %v", result) for _, value := range utils.OutputMaps(result) {
logger.Debugf("%s", value)
}
return nil return nil
} }
@@ -433,15 +435,15 @@ func insertDataToDB(result map[string]string) error {
lesArgs := fmt.Sprintf("%s vnc vncip=%s vncpassword=sunhpc", result["boot_args"], result["private_address"]) lesArgs := fmt.Sprintf("%s vnc vncip=%s vncpassword=sunhpc", result["boot_args"], result["private_address"])
bootaction := []string{ bootaction := []string{
fmt.Sprintf("insert into bootactions values (1, 'install', '%s', '%s', '%s');", fmt.Sprintf("insert or replace into bootactions values (1, 'install', '%s', '%s', '%s');",
vmlinuz, initrds, insArgs), vmlinuz, initrds, insArgs),
"insert into bootactions values (2, 'os', 'localboot 0', '', '');", "insert or replace into bootactions values (2, 'os', 'localboot 0', '', '');",
"insert into bootactions values (3, 'memtest', 'kernel memtest', '', '');", "insert or replace into bootactions values (3, 'memtest', 'kernel memtest', '', '');",
fmt.Sprintf("insert into bootactions values (4, 'install headless', '%s', '%s', '%s');", fmt.Sprintf("insert or replace into bootactions values (4, 'install headless', '%s', '%s', '%s');",
vmlinuz, initrds, lesArgs), vmlinuz, initrds, lesArgs),
fmt.Sprintf("insert into bootactions values (5, 'rescue', '%s', '%s', '%s');", fmt.Sprintf("insert or replace into bootactions values (5, 'rescue', '%s', '%s', '%s');",
vmlinuz, initrds, resArgs), vmlinuz, initrds, resArgs),
"insert into bootactions values (6, 'pxeflash', 'kernel memdisk bigraw', 'pxeflash.img', 'keeppxe');", "insert or replace into bootactions values (6, 'pxeflash', 'kernel memdisk bigraw', 'pxeflash.img', 'keeppxe');",
} }
insertData = append(insertData, bootaction...) insertData = append(insertData, bootaction...)
@@ -453,42 +455,41 @@ func insertDataToDB(result map[string]string) error {
category := item.Category category := item.Category
catindex := item.Catindex catindex := item.Catindex
insertData = append(insertData, insertData = append(insertData,
fmt.Sprintf("insert into attributes values ('%s', '%s', '%s', %d, %d);", fmt.Sprintf("insert or replace into attributes values (NULL, '%s', '%s', '%s', %d, %d);",
key, value, shadow, category, catindex)) key, value, shadow, category, catindex))
} }
nodes := []string{ nodes := []string{
fmt.Sprintf( fmt.Sprintf(
"insert into nodes values (1, '%s', '%d', 0, 0, '%s', '%s', '', 'install');", "insert or replace into nodes values (1, '%s', '2', '%d', 0, 0, '%s', '%s', '', 'install');",
result["private_hostname"], result["private_hostname"],
info.GetSystemInfo().NumCPU, info.GetSystemInfo().NumCPU,
info.GetSystemInfo().Arch, info.GetSystemInfo().Arch,
info.GetSystemInfo().OS), info.GetSystemInfo().OS),
fmt.Sprintf( fmt.Sprintf(
"insert into networks values (1, 1, '%s', '%s', '%s', '%s', '2');", `insert or replace into subnets values (1, 'private', '%s', '%s', '%s', '%s', '1');`,
result["public_mac"],
result["public_address"],
result["private_hostname"],
result["public_interface"]),
fmt.Sprintf(
"insert into networks values (2, 1, '%s', '%s', '%s', '%s', '1');",
result["private_mac"],
result["private_address"],
result["private_hostname"],
result["private_interface"]),
fmt.Sprintf(
"insert into subnets values (1, 'private', '%s', '%s', '%s', '%s', '1');",
result["private_domain"], result["private_domain"],
result["private_network"], result["private_network"],
result["private_netmask"], result["private_netmask"],
result["private_mtu"]), result["private_mtu"]),
fmt.Sprintf( fmt.Sprintf(
"insert into subnets values (2, 'public', '%s', '%s', '%s', '%s', '0');", `insert or replace into subnets values (2, 'public', '%s', '%s', '%s', '%s', '0');`,
result["public_domain"], result["public_domain"],
result["public_network"], result["public_network"],
result["public_netmask"], result["public_netmask"],
result["public_mtu"]), result["public_mtu"]),
fmt.Sprintf(
`insert or replace into networks values (1, 1, '%s', '%s', '%s', '%s', '2', NULL, NULL,NULL,NULL);`,
result["public_mac"],
result["public_address"],
result["private_hostname"],
result["public_interface"]),
fmt.Sprintf(
`insert or replace into networks values (2, 1, '%s', '%s', '%s', '%s', '1', NULL, NULL, NULL, NULL);`,
result["private_mac"],
result["private_address"],
result["private_hostname"],
result["private_interface"]),
} }
insertData = append(insertData, nodes...) insertData = append(insertData, nodes...)

View File

@@ -227,7 +227,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch msg.String() {
case "ctrl+c": case "ctrl+c":
m.saveConfig()
//m.quitting = true //m.quitting = true
return m, tea.Quit return m, tea.Quit
@@ -257,6 +256,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// 页6确认配置 → 退出并保存 // 页6确认配置 → 退出并保存
case "confirm_btn": case "confirm_btn":
m.saveConfig()
m.done = true m.done = true
//m.quitting = true //m.quitting = true
return m, tea.Quit return m, tea.Quit

View File

@@ -24,11 +24,12 @@ var (
// 基础布局样式 // 基础布局样式
appStyle = lipgloss.NewStyle(). appStyle = lipgloss.NewStyle().
Padding(1, 1). Padding(1, 1).
MarginBottom(1).
BorderStyle(lipgloss.RoundedBorder()). BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(primaryColor). BorderForeground(primaryColor).
Foreground(textColor). Foreground(textColor).
Align(lipgloss.Center). Align(lipgloss.Center)
Height(40) //Height(40)
// 标题样式 // 标题样式
titleStyle = lipgloss.NewStyle(). titleStyle = lipgloss.NewStyle().