Tui 模块开发完成,数据正常写入数据库

This commit is contained in:
2026-03-06 16:33:08 +08:00
parent 3f5e333a4d
commit 8bc4f4fe04
7 changed files with 674 additions and 535 deletions

View File

@@ -3,6 +3,7 @@ package database
import (
"database/sql"
"fmt"
"strings"
"sync"
"sunhpc/pkg/config"
@@ -22,22 +23,20 @@ var (
dbErr error
)
// =========================================================
// 封装数据库函数使用Go实现
// =========================================================
// MapCategory - 根据类别名称查ID
// 查询方式: globalID, err := db.MapCategory(conn, "global")
func MapCategory(conn *sql.DB, catname string) (int, error) {
var id int
query := "select id from categories where name = ?"
logger.Debugf("查询SQL: %s", query)
logger.Debugf("查询类别ID: %s", catname)
fullSQL := ReplaceSQLQuery(query, catname)
err := conn.QueryRow(query, catname).Scan(&id)
if err == sql.ErrNoRows {
logger.Debugf("未找到类别 %s, 返回ID=0", catname)
return 0, nil // 无匹配返回0
}
logger.Debugf("查询到类别 %s, ID=%d", catname, id)
logger.Debugf("查询语句: %s , CatName=%s, ID=%d", fullSQL, catname, id)
return id, nil
}
@@ -49,14 +48,15 @@ func MapCategoryIndex(conn *sql.DB, catindexName, categoryIndex string) (int, er
select index_id from vmapCategoryIndex
where categoryName = ? and categoryIndex = ?
`
logger.Debugf("查询SQL: %s", query)
logger.Debugf("查询索引ID: %s, 类别: %s", catindexName, categoryIndex)
fullSQL := ReplaceSQLQuery(query, catindexName, categoryIndex)
err := conn.QueryRow(query, catindexName, categoryIndex).Scan(&id)
if err == sql.ErrNoRows {
logger.Debugf("未找到索引 %s, 返回ID=0", catindexName)
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
}
@@ -165,6 +165,13 @@ func GetDB() (*sql.DB, error) {
return
}
var version string
err = sqlDB.QueryRow("select sqlite_version()").Scan(&version)
if err != nil {
version = "unknown"
}
logger.Debugf("数据库版本: %s", version)
logger.Debug("数据库连接成功")
dbInstance = sqlDB
})
@@ -178,10 +185,44 @@ func GetDB() (*sql.DB, 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 中的函数
//for _, ddl := range CreateTableStatements() {
for _, ddl := range BaseTables() {
logger.Debugf("执行: %s", ddl)
for name, ddl := range BaseTables() {
// 删除表或者试图(如果存在)
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 {
return fmt.Errorf("数据表创建失败: %w", err)
}
@@ -291,28 +332,36 @@ func ExecWithTransaction(ddl []string) error {
return err
}
var finished bool
// 延迟处理:如果函数异常,回滚事务
defer func() {
if r := recover(); r != nil {
// 捕获 panic 并回滚事务
tx.Rollback()
logger.Errorf("事务执行中发生 panic: %v", r)
if !finished {
// 捕获 panic 并回滚事务
tx.Rollback()
logger.Errorf("事务执行中发生 panic: %v", r)
}
panic(r)
}
}()
// 遍历执行 DDL 语句
for idx, sql := range ddl {
logger.Debugf("执行 DDL 语句 %d: %s", idx+1, sql)
_, err = tx.Exec(sql)
if err != nil {
// 执行失败时,回滚事务
rollbackErr := tx.Rollback()
finished = true // 标记事务已完成
if rollbackErr != nil {
logger.Errorf("执行失败: 回滚失败: %v (原错误: %v, SQL: %s)", rollbackErr, err, sql)
} else {
logger.Errorf("执行失败: 回滚事务: %v, SQL: %s", 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
}
finished = true // 标记事务已完成
logger.Debugf("成功执行 %d 条 SQL 语句, 事务已提交.", len(ddl))
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 (
"database/sql"
"fmt"
"sunhpc/pkg/logger"
)
func BaseTables() []string {
func BaseTables() map[string]string {
datalist := []string{}
Appliances := `
CREATE TABLE IF NOT EXISTS appliances (
ID integer primary key autoincrement,
Name varchar(32) not null default '',
Graph varchar(64) not null default 'default',
Node varchar(64) not null default '',
OS varchar(64) not null default 'linux'
);
`
datalist = append(datalist, Appliances)
Memberships := `
CREATE TABLE IF NOT EXISTS memberships (
ID integer primary key autoincrement,
Name varchar(64) not null default '',
Appliance integer(11) default '0',
Distribution integer(11) default '1',
Public varchar(64) not null default 'no'
);
`
datalist = append(datalist, Memberships)
Categories := `
CREATE TABLE IF NOT EXISTS categories (
ID integer primary key autoincrement,
Name varchar(64) not null unique default '0',
Description varchar(255) default null,
UNIQUE(Name)
);
`
datalist = append(datalist, Categories)
Catindex := `
CREATE TABLE IF NOT EXISTS catindex (
ID integer primary key autoincrement,
Name varchar(64) not null unique default '0',
Category integer not null,
Foreign key(Category) references categories(ID) on delete cascade
);
`
datalist = append(datalist, Catindex)
Resolvechain := `
CREATE TABLE IF NOT EXISTS resolvechain (
ID integer primary key autoincrement,
Name varchar(64) not null default '0',
Category integer(11) not null,
Precedence integer(11) not null default '10',
UNIQUE(Name, Category)
Foreign key(Category) references categories(ID) on delete cascade
);
`
datalist = append(datalist, Resolvechain)
Nodes := `
CREATE TABLE IF NOT EXISTS nodes (
ID integer primary key autoincrement,
Name varchar(128) default null,
Membership integer(11) default '2',
CPUs integer(11) not null default '1',
Rack varchar(11) default null,
Rank integer(11) default null,
Arch varchar(32) default null,
OS varchar(64) not null default 'linux',
RunAction varchar(64) default 'os',
InstallAction varchar(64) default 'install'
);
create index if not exists idx_nodes_name on nodes(Name);
`
datalist = append(datalist, Nodes)
Aliases := `
CREATE TABLE IF NOT EXISTS aliases (
ID integer primary key autoincrement,
Node integer(15) not null default '0',
Name varchar(32) default null,
Foreign key(Node) references nodes(ID) on delete cascade
);
`
datalist = append(datalist, Aliases)
Networks := `
CREATE TABLE IF NOT EXISTS networks (
ID integer primary key autoincrement,
Node integer(11) default null,
MAC varchar(64) default null,
IP varchar(64) default null,
Name varchar(128) default null,
Device varchar(32) default null,
Subnet integer(11) default null,
Module varchar(128) default null,
VlanID integer(11) default null,
Options varchar(128) default null,
Channel varchar(128) default null,
Foreign key(Node) references nodes(ID) on delete cascade,
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`
datalist = append(datalist, Networks)
GlobalRoutes := `
CREATE TABLE IF NOT EXISTS globalroutes (
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer(11) default null,
Primary key(Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`
datalist = append(datalist, GlobalRoutes)
OSRoutes := `
CREATE TABLE IF NOT EXISTS osroutes (
OS varchar(64) not null default 'linux',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer(11) default null,
Primary key(OS, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`
datalist = append(datalist, OSRoutes)
ApplianceRoutes := `
CREATE TABLE IF NOT EXISTS applianceroutes (
Appliance varchar(11) not null default '0',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer(11) default null,
Primary key(Appliance, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`
datalist = append(datalist, ApplianceRoutes)
NodeRoutes := `
CREATE TABLE IF NOT EXISTS noderoutes (
Node varchar(11) not null default '0',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer(11) default null,
Primary key(Node, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`
datalist = append(datalist, NodeRoutes)
Subnets := `
CREATE TABLE IF NOT EXISTS subnets (
ID integer primary key autoincrement,
name varchar(32) unique not null,
dnszone varchar(64) unique not null,
subnet varchar(32) default null,
netmask varchar(32) default null,
mtu integer(11) default '1500',
servedns boolean default false
);
`
datalist = append(datalist, Subnets)
PublicKeys := `
CREATE TABLE IF NOT EXISTS publickeys (
ID integer primary key autoincrement,
Node integer(11) not null default '0',
Public_Key varchar(8192) default null,
Description varchar(8192) default null,
Foreign key(Node) references nodes(ID) on delete cascade
);
`
datalist = append(datalist, PublicKeys)
SecGlobal := `
CREATE TABLE IF NOT EXISTS secglobal (
Attr varchar(128) default null,
Value text,
Enc varchar(128) default null,
Primary key(Attr)
);
`
datalist = append(datalist, SecGlobal)
SecNodes := `
CREATE TABLE IF NOT EXISTS secnodes (
Attr varchar(128) default null,
Enc varchar(128) default null,
Value text,
Node integer(15) not null default '0',
Primary key(Attr, Node)
);
`
datalist = append(datalist, SecNodes)
Attributes := `
CREATE TABLE IF NOT EXISTS attributes (
ID integer primary key autoincrement,
Attr varchar(128) not null,
Value text,
Shadow text,
Category integer(11) not null,
Catindex integer(11) not null,
UNIQUE(Attr, Category, Catindex),
Foreign key(Catindex) references catindex(ID) on delete cascade
);
`
datalist = append(datalist, Attributes)
Partitions := `
CREATE TABLE IF NOT EXISTS partitions (
ID integer primary key autoincrement,
Node integer(15) not null default '0',
Device varchar(128) not null default '',
MountPoint varchar(128) not null default '',
SectorStart varchar(128) not null default '',
PartitionSize varchar(128) not null default '',
FsType varchar(128) not null default '',
PartitionFlags varchar(128) not null default '',
FormatFlags varchar(128) not null default ''
);
`
datalist = append(datalist, Partitions)
Firewalls := `
CREATE TABLE IF NOT EXISTS firewalls (
ID integer primary key autoincrement,
Rulename varchar(128) not null,
Rulesrc varchar(256) not null default 'custom',
InSubnet int(11),
OutSubnet int(11),
Service varchar(256),
Protocol varchar(256),
Action varchar(256),
Chain varchar(256),
Flags varchar(256),
Comment varchar(256),
Category integer(11) not null,
Catindex integer(11) not null,
Check(rulesrc IN ('system', 'custom'))
UNIQUE(Rulename, Category, Catindex),
Foreign key(Catindex) references catindex(ID) on delete cascade
);
`
datalist = append(datalist, Firewalls)
Rolls := `
CREATE TABLE IF NOT EXISTS rolls (
ID integer primary key autoincrement,
Name varchar(128) not null default '',
Version varchar(32) not null default '',
Arch varchar(32) not null default '',
OS varchar(64) not null default 'linux',
Enabled varchar(3) not null default 'yes',
Check(Enabled IN ('yes', 'no'))
Check(OS IN ('linux', 'other'))
);
`
datalist = append(datalist, Rolls)
NodeRolls := `
CREATE TABLE IF NOT EXISTS noderolls (
Node varchar(11) not null default '0',
RollID varchar(11) not null,
Primary key(Node, RollID)
);
`
datalist = append(datalist, NodeRolls)
Bootactions := `
CREATE TABLE IF NOT EXISTS bootactions (
ID integer primary key autoincrement,
Action varchar(256) default null,
Kernel varchar(256) default null,
Ramdisk varchar(256) default null,
Args varchar(1024) default null
);
`
datalist = append(datalist, Bootactions)
BootFlags := `
CREATE TABLE IF NOT EXISTS bootflags (
ID integer primary key autoincrement,
Node integer(11) not null default '0',
Flags varchar(256) default null
);
`
datalist = append(datalist, BootFlags)
Distributions := `
CREATE TABLE IF NOT EXISTS distributions (
ID integer primary key autoincrement,
Name varchar(32) not null default '',
OS varchar(32) default '',
Release varchar(32) default ''
);
`
datalist = append(datalist, Distributions)
View_vnet := `
DROP VIEW IF EXISTS vnet;
CREATE VIEW vnet AS
SELECT
n.name AS nodename, /* 查询nodes表中name字段,将字段改名为nodename */
m.name AS membership,
a.name AS appliance,
n.rack, n.rank, /* 查询nodes表中rack和rank字段,使用原始字段名 */
s.name AS subnet,
nt.ip, nt.device, nt.module,
nt.name AS hostname,
s.dnszone AS domainname,
s.netmask, s.mtu
FROM
nodes n /* 主表: 先查询nodes表,别名n */
inner join memberships m on n.membership=m.id /* 连接memberships表,on只保留满足条件的行 */
inner join appliances a on m.appliance=a.id /* 连接appliances表,on只保留满足条件的行 */
inner join networks nt on n.id=nt.node /* 连接networks表,on只保留满足条件的行 */
inner join subnets s on nt.subnet=s.id /* 连接subnets表,on只保留满足条件的行 */
;
`
datalist = append(datalist, View_vnet)
View_hostselections := `
DROP VIEW IF EXISTS hostselections;
CREATE VIEW hostselections AS
SELECT
n.name AS host,
c.id as category,
ci.id as selection
FROM
nodes n
inner join memberships m on n.membership=m.id -- 节点表关联所属分组
inner join appliances a on m.appliance=a.id -- 分组关联所属应用角色
inner join categories c on
-- 匹配4类分层配置的category(全局/OS/应用/主机)
c.name in ('global', 'os', 'appliance', 'host')
inner join catindex ci on
-- 核心匹配逻辑: category和catindex的name字段一一对应
(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
(c.name = 'host' and ci.name = n.name)
;
`
datalist = append(datalist, View_hostselections)
View_vcatindex := `
-- 视图vcatindex: 类别索引可读试图
DROP VIEW IF EXISTS vcatindex;
CREATE VIEW vcatindex AS
SELECT
c.id AS ID,
cat.Name AS Category,
ci.Name AS catindex
FROM
categories cat
inner join catindex ci on ci.category=cat.id
;
`
datalist = append(datalist, View_vcatindex)
View_vresolvechain := `
-- 视图vresolvechain: 解析链可读试图
DROP VIEW IF EXISTS vresolvechain;
CREATE VIEW vresolvechain AS
SELECT
r.name AS chain,
cat.name AS category,
precedence
FROM
resolvechain r
inner join categories cat on r.category=cat.id
order by chain, precedence
;
`
datalist = append(datalist, View_vresolvechain)
View_vattributes := `
-- 视图vattributes: 属性可读试图
DROP VIEW IF EXISTS vattributes;
CREATE VIEW vattributes AS
SELECT
a.id,
attr,
value,
shadow,
cat.name AS category,
ci.name AS catindex
FROM
attributes a
inner join catindex ci on a.catindex=ci.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
return map[string]string{
"appliances": `
CREATE TABLE IF NOT EXISTS appliances (
ID integer primary key autoincrement,
Name varchar(32) not null default '',
Graph varchar(64) not null default 'default',
Node varchar(64) not null default '',
OS varchar(64) not null default 'linux'
);
`,
"memberships": `
CREATE TABLE IF NOT EXISTS memberships (
ID integer primary key autoincrement,
Name varchar(64) not null default '',
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,
Name varchar(64) not null unique default '0',
Description varchar(255) default null,
UNIQUE(Name)
);
`,
"catindex": `
CREATE TABLE IF NOT EXISTS catindex (
ID integer primary key autoincrement,
Name varchar(64) not null unique default '0',
Category integer not null,
Foreign key(Category) references categories(ID) on delete cascade
);
`,
"resolvechain": `
CREATE TABLE IF NOT EXISTS resolvechain (
ID integer primary key autoincrement,
Name varchar(64) not null default '0',
Category integer(11) not null,
Precedence integer(11) not null default '10',
UNIQUE(Name, Category)
Foreign key(Category) references categories(ID) on delete cascade
);
`,
"nodes": `
CREATE TABLE IF NOT EXISTS nodes (
ID integer primary key autoincrement,
Name varchar default null,
Membership integer default '2',
CPUs integer not null default '1',
Rack varchar default null,
Rank integer default null,
Arch varchar default null,
OS varchar default null,
RunAction varchar(64) default 'os',
InstallAction varchar(64) default 'install'
);
create index if not exists idx_nodes_name on nodes(Name);
`,
"aliases": `
CREATE TABLE IF NOT EXISTS aliases (
ID integer primary key autoincrement,
Node integer not null default '0',
Name varchar default null,
Foreign key(Node) references nodes(ID) on delete cascade
);
create index if not exists idx_aliases_name on aliases(Name);
`,
"networks": `
CREATE TABLE IF NOT EXISTS networks (
ID integer primary key autoincrement,
Node integer not null default '0',
MAC varchar default null,
IP varchar default null,
Name varchar default null,
Device varchar default null,
Subnet integer default null,
Module varchar default null,
VlanID integer default null,
Options varchar default null,
Channel varchar default null,
Foreign key(Node) references nodes(ID) on delete cascade,
Foreign key(Subnet) references subnets(ID) on delete cascade
);
create index if not exists idx_networks_name on networks(Name);
`,
"globalroutes": `
CREATE TABLE IF NOT EXISTS globalroutes (
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer default null,
Primary key(Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`,
"osroutes": `
CREATE TABLE IF NOT EXISTS osroutes (
OS varchar(64) not null default 'linux',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer default null,
Primary key(OS, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`,
"applianceroutes": `
CREATE TABLE IF NOT EXISTS applianceroutes (
Appliance varchar(11) not null default '0',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer default null,
Primary key(Appliance, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`,
"noderoutes": `
CREATE TABLE IF NOT EXISTS noderoutes (
Node varchar(11) not null default '0',
Network varchar(32) not null default '',
Netmask varchar(32) not null default '',
Gateway varchar(32) not null default '',
Subnet integer default null,
Primary key(Node, Network, Netmask)
Foreign key(Subnet) references subnets(ID) on delete cascade
);
`,
"subnets": `
CREATE TABLE IF NOT EXISTS subnets (
ID integer primary key autoincrement,
name varchar(32) unique not null,
dnszone varchar(64) unique not null,
subnet varchar(32) default null,
netmask varchar(32) default null,
mtu integer(11) default '1500',
servedns boolean default false
);
`,
"publickeys": `
CREATE TABLE IF NOT EXISTS publickeys (
ID integer primary key autoincrement,
Node integer(11) not null default '0',
Public_Key varchar(8192) default null,
Description varchar(8192) default null,
Foreign key(Node) references nodes(ID) on delete cascade
);
`,
"secglobal": `
CREATE TABLE IF NOT EXISTS secglobal (
Attr varchar(128) default null,
Value text,
Enc varchar(128) default null,
Primary key(Attr)
);
`,
"secnodes": `
CREATE TABLE IF NOT EXISTS secnodes (
Attr varchar(128) default null,
Enc varchar(128) default null,
Value text,
Node integer(15) not null default '0',
Primary key(Attr, Node)
);
`,
"attributes": `
CREATE TABLE IF NOT EXISTS attributes (
ID integer primary key autoincrement,
Attr varchar(128) not null,
Value text,
Shadow text,
Category integer(11) not null,
Catindex integer(11) not null,
UNIQUE(Attr, Category, Catindex),
Foreign key(Catindex) references catindex(ID) on delete cascade
);
`,
"partitions": `
CREATE TABLE IF NOT EXISTS partitions (
ID integer primary key autoincrement,
Node integer(15) not null default '0',
Device varchar(128) not null default '',
MountPoint varchar(128) not null default '',
SectorStart varchar(128) not null default '',
PartitionSize varchar(128) not null default '',
FsType varchar(128) not null default '',
PartitionFlags varchar(128) not null default '',
FormatFlags varchar(128) not null default ''
);
`,
"firewalls": `
CREATE TABLE IF NOT EXISTS firewalls (
ID integer primary key autoincrement,
Rulename varchar(128) not null,
Rulesrc varchar(256) not null default 'custom',
InSubnet int(11),
OutSubnet int(11),
Service varchar(256),
Protocol varchar(256),
Action varchar(256),
Chain varchar(256),
Flags varchar(256),
Comment varchar(256),
Category integer(11) not null,
Catindex integer(11) not null,
Check(rulesrc IN ('system', 'custom'))
UNIQUE(Rulename, Category, Catindex),
Foreign key(Catindex) references catindex(ID) on delete cascade
);
`,
"rolls": `
CREATE TABLE IF NOT EXISTS rolls (
ID integer primary key autoincrement,
Name varchar(128) not null default '',
Version varchar(32) not null default '',
Arch varchar(32) not null default '',
OS varchar(64) not null default 'linux',
Enabled varchar(3) not null default 'yes',
Check(Enabled IN ('yes', 'no'))
Check(OS IN ('linux', 'other'))
);
`,
"noderolls": `
CREATE TABLE IF NOT EXISTS noderolls (
Node varchar(11) not null default '0',
RollID varchar(11) not null,
Primary key(Node, RollID)
);
`,
"bootactions": `
CREATE TABLE IF NOT EXISTS bootactions (
ID integer primary key autoincrement,
Action varchar(256) default null,
Kernel varchar(256) default null,
Ramdisk varchar(256) default null,
Args varchar(1024) default null
);
`,
"bootflags": `
CREATE TABLE IF NOT EXISTS bootflags (
ID integer primary key autoincrement,
Node integer(11) not null default '0',
Flags varchar(256) default null
);
`,
"distributions": `
CREATE TABLE IF NOT EXISTS distributions (
ID integer primary key autoincrement,
Name varchar(32) not null default '',
OS varchar(32) default '',
Release varchar(32) default ''
);
`,
"vnet": `
DROP VIEW IF EXISTS vnet;
CREATE VIEW vnet AS
SELECT
n.name AS nodename, /* 查询nodes表中name字段,将字段改名为nodename */
m.name AS membership,
a.name AS appliance,
n.rack, n.rank, /* 查询nodes表中rack和rank字段,使用原始字段名 */
s.name AS subnet,
nt.ip, nt.device, nt.module,
nt.name AS hostname,
s.dnszone AS domainname,
s.netmask, s.mtu
FROM
nodes n /* 主表: 先查询nodes表,别名n */
inner join memberships m on n.membership=m.id /* 连接memberships表,on只保留满足条件的行 */
inner join appliances a on m.appliance=a.id /* 连接appliances表,on只保留满足条件的行 */
inner join networks nt on n.id=nt.node /* 连接networks表,on只保留满足条件的行 */
inner join subnets s on nt.subnet=s.id /* 连接subnets表,on只保留满足条件的行 */
;
`,
"hostselections": `
DROP VIEW IF EXISTS hostselections;
CREATE VIEW hostselections AS
SELECT
n.name AS host,
c.id as category,
ci.id as selection
FROM
nodes n
inner join memberships m on n.membership=m.id -- 节点表关联所属分组
inner join appliances a on m.appliance=a.id -- 分组关联所属应用角色
inner join categories c on
-- 匹配4类分层配置的category(全局/OS/应用/主机)
c.name in ('global', 'os', 'appliance', 'host')
inner join catindex ci on
-- 核心匹配逻辑: category和catindex的name字段一一对应
(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
(c.name = 'host' and ci.name = n.name)
;
`,
"vcatindex": `
-- 视图vcatindex: 类别索引可读试图
DROP VIEW IF EXISTS vcatindex;
CREATE VIEW vcatindex AS
SELECT
c.id AS ID,
cat.Name AS Category,
ci.Name AS catindex
FROM
categories cat
inner join catindex ci on ci.category=cat.id
;
`,
"vresolvechain": `
-- 视图vresolvechain: 解析链可读试图
DROP VIEW IF EXISTS vresolvechain;
CREATE VIEW vresolvechain AS
SELECT
r.name AS chain,
cat.name AS category,
precedence
FROM
resolvechain r
inner join categories cat on r.category=cat.id
order by chain, precedence
;
`,
"vattributes": `
-- 视图vattributes: 属性可读试图
DROP VIEW IF EXISTS vattributes;
CREATE VIEW vattributes AS
SELECT
a.id,
attr,
value,
shadow,
cat.name AS category,
ci.name AS catindex
FROM
attributes a
inner join catindex ci on a.catindex=ci.id
inner join categories cat on a.category=cat.id
order by attr, catindex, category
;
`,
"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
;
`,
"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
;
`,
"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
;
`,
}
}
func InitBaseData(conn *sql.DB) error {
logger.Debug("初始化基础数据...")
// ========== 第一步:插入 categories 数据 ==========
categoryData := []struct {
Name string
@@ -484,15 +423,26 @@ func InitBaseData(conn *sql.DB) error {
}
// 批量插入 categories (忽略重复)
logger.Debug("插入 categories 数据...")
for _, cd := range categoryData {
query := `
insert or ignore into categories (Name, Description)
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 {
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 数据 ==========
@@ -511,7 +461,9 @@ func InitBaseData(conn *sql.DB) error {
{"devel-server", "appliance"},
{"login", "appliance"},
}
// 批量插入 catindex (忽略重复)
logger.Debug("插入 Catindex 数据...")
for _, ci := range catindexData {
// 动态获取类别ID (复用MapCategory函数)
catID, err := MapCategory(conn, ci.Category)
@@ -527,10 +479,19 @@ func InitBaseData(conn *sql.DB) error {
insert or ignore into catindex (Name, Category)
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 {
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 数据 ==========
@@ -546,6 +507,7 @@ func InitBaseData(conn *sql.DB) error {
{"default", "host", 50},
}
// 批量插入 resolvechain (忽略重复)
logger.Debugf("插入 resolvechain 数据...")
for _, rcd := range resolveChainData {
// 动态获取类别ID (复用MapCategory函数)
catID, err := MapCategory(conn, rcd.Category)
@@ -561,10 +523,19 @@ func InitBaseData(conn *sql.DB) error {
insert or ignore into resolvechain (Name, Category, Precedence)
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 {
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

View File

@@ -80,6 +80,72 @@ type LogConfig struct {
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{
Level: "info",
@@ -216,34 +282,7 @@ func Init(cfg LogConfig) {
// 1. 创建logrus实例
logrusInst := logrus.New()
// 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. 配置日志级别
// 2. 配置日志级别(总开关,必须在输出配置前)
lvl, err := logrus.ParseLevel(cfg.Level)
if err != nil {
lvl = logrus.InfoLevel // 解析失败默认Info级别
@@ -257,6 +296,45 @@ func Init(cfg LogConfig) {
// 启用文件行号(必须开启否则getCallerInfo拿不到数据)
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. 赋值给全局默认实例
DefaultLogger = &logrusLogger{logrusInst}
})

View File

@@ -3,6 +3,7 @@ package utils
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"os/exec"
"time"
@@ -32,6 +33,29 @@ func GetTimestamp() string {
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 (
NoAvailableNetworkInterfaces = "No available network interfaces"

View File

@@ -201,7 +201,7 @@ func loadConfig() (*ConfigMapping, error) {
if _, err := toml.DecodeFile(cfgfile, configs); err != nil {
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
}
// 其他错误,返回错误
@@ -209,7 +209,7 @@ func loadConfig() (*ConfigMapping, error) {
return nil, err
}
logger.Infof("Load config file %s success", cfgfile)
logger.Debugf("Load config file %s success", cfgfile)
return configs, nil
}
@@ -310,7 +310,9 @@ func (m *model) saveConfig() error {
return err
}
logger.Debugf("Result: %v", result)
for _, value := range utils.OutputMaps(result) {
logger.Debugf("%s", value)
}
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"])
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),
"insert into bootactions values (2, 'os', 'localboot 0', '', '');",
"insert into bootactions values (3, 'memtest', 'kernel memtest', '', '');",
fmt.Sprintf("insert into bootactions values (4, 'install headless', '%s', '%s', '%s');",
"insert or replace into bootactions values (2, 'os', 'localboot 0', '', '');",
"insert or replace into bootactions values (3, 'memtest', 'kernel memtest', '', '');",
fmt.Sprintf("insert or replace into bootactions values (4, 'install headless', '%s', '%s', '%s');",
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),
"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...)
@@ -453,42 +455,41 @@ func insertDataToDB(result map[string]string) error {
category := item.Category
catindex := item.Catindex
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))
}
nodes := []string{
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"],
info.GetSystemInfo().NumCPU,
info.GetSystemInfo().Arch,
info.GetSystemInfo().OS),
fmt.Sprintf(
"insert into networks values (1, 1, '%s', '%s', '%s', '%s', '2');",
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');",
`insert or replace into subnets values (1, 'private', '%s', '%s', '%s', '%s', '1');`,
result["private_domain"],
result["private_network"],
result["private_netmask"],
result["private_mtu"]),
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_network"],
result["public_netmask"],
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...)

View File

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

View File

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