This commit is contained in:
IT 老营长 @云轩领航-创始人 2024-09-18 13:32:53 +08:00
parent 1480f77d57
commit 91ac804ddb
23 changed files with 139 additions and 186 deletions

View File

@ -1,48 +0,0 @@
package dashboard
import (
"shylinux.com/x/ice"
kit "shylinux.com/x/toolkits"
"shylinux.com/x/operation/src/dashboard/model"
)
type client struct {
Table
fields string `data:"host,port,username,database"`
config string `name:"config host* port* username* password* database*" role:"leader"`
}
func (s client) List(m *ice.Message, arg ...string) {
if len(arg) == 1 {
s.SelectDetail(m, model.UID, arg[0])
if m.Append(model.USERNAME) == "" {
m.SetAppend().EchoInfoButton("", s.Config)
} else {
m.PushAction(s.Config).Action(s.Config)
s.UserPlaceInit(m)
}
}
}
func (s client) Config(m *ice.Message, arg ...string) {
s.Update(m, kit.Dict(arg), model.UID, m.Option(model.CLIENT_UID))
s.UserPlaceInit(m)
}
func init() { ice.TeamCtxCmd(client{}) }
type ClientType int
const (
ClientPlace ClientType = iota
ClientMySQL
ClientRedis
)
var ClientTypeList = map[ClientType]string{
ClientPlace: "place",
ClientMySQL: "mysql",
ClientRedis: "redis",
}
func (s ClientType) String() string { return ClientTypeList[s] }

View File

@ -10,15 +10,20 @@ import (
type Table struct {
operation.Table
list string `name:"list client_uid uid auto" role:"void"`
list string `name:"list dashboard_uid uid auto" role:"leader,worker,server"`
}
func (s Table) Inputs(m *ice.Message, arg ...string) {
switch arg[0] {
case model.USER_CLIENT_ROLE:
s.InputsListRole(m, UserClientRoleList, arg...)
case model.CLIENT_TYPE:
s.InputsList(m, ClientTypeList, arg...)
case model.USER_DASHBOARD_ROLE:
s.InputsListRole(m, UserDashboardRoleList, arg...)
case model.DASHBOARD_TYPE:
for v, k := range DashboardTypeList {
if m.IsTech() || v > 0 {
m.Push(arg[0], v).Push(model.NAME, k)
}
}
m.SortInt(arg[0]).DisplayInputKeyNameIconTitle()
default:
s.Table.Inputs(m, arg...)
}
@ -26,10 +31,10 @@ func (s Table) Inputs(m *ice.Message, arg ...string) {
func (s Table) RewriteAppend(m *ice.Message, arg ...string) *ice.Message {
m.RewriteAppend(func(value, key string, index int) string {
switch key {
case model.USER_CLIENT_ROLE:
value = UserClientRole(kit.Int(value)).String()
case model.CLIENT_TYPE:
value = ClientType(kit.Int(value)).String()
case model.USER_DASHBOARD_ROLE:
value = UserDashboardRole(kit.Int(value)).String()
case model.DASHBOARD_TYPE:
value = DashboardType(kit.Int(value)).String()
}
return value
})

View File

@ -11,10 +11,10 @@ const (
CONTENT = "content"
CREATED_AT = "created_at"
USER_UID = "user_uid"
USER_CLIENT_ROLE = "user_client_role"
CLIENT_UID = "client_uid"
CLIENT_NAME = "client_name"
CLIENT_TYPE = "client_type"
USER_DASHBOARD_ROLE = "user_dashboard_role"
DASHBOARD_UID = "dashboard_uid"
DASHBOARD_NAME = "dashboard_name"
DASHBOARD_TYPE = "dashboard_type"
SUMMARY_UID = "summary_uid"
COMPANY_UID = "company_uid"
CITY_UID = "city_uid"
@ -30,11 +30,11 @@ const (
SCORE = "score"
)
type UserClient struct {
type UserDashboard struct {
db.ModelUserPlace
ClientUID string `gorm:"type:char(32);index"`
DashboardUID string `gorm:"type:char(32);index"`
}
type Client struct {
type Dashboard struct {
db.ModelPlace
CompanyUID string `gorm:"type:char(32);index"`
Host string `gorm:"type:varchar(32)"`
@ -45,7 +45,7 @@ type Client struct {
}
type Summary struct {
db.ModelContent
ClientUID string `gorm:"type:char(32);index"`
DashboardUID string `gorm:"type:char(32);index"`
Space string `gorm:"type:varchar(64)"`
Index string `gorm:"type:varchar(128)"`
Query string `gorm:"type:varchar(255)"`
@ -54,4 +54,4 @@ type Summary struct {
Score float32 `gorm:"default:0"`
}
func init() { db.CmdModels("", &UserClient{}, &Client{}, &Summary{}) }
func init() { db.CmdModels("", &UserDashboard{}, &Dashboard{}, &Summary{}) }

View File

@ -7,7 +7,9 @@ import (
type Portal struct {
operation.Portal
placeCreate string `name:"placeCreate city_name* company_name* client_name* client_type*:select" role:"void"`
placeCreate string `name:"placeCreate city_name* company_name* dashboard_name* dashboard_type*:select" role:"void"`
}
func init() { gonganxitong.PortalCmd(Portal{Portal: operation.NewPortal(userClient{}, client{})}) }
func init() {
gonganxitong.PortalCmd(Portal{Portal: operation.NewPortal(userDashboard{}, dashboard{})})
}

View File

@ -1,21 +1,23 @@
{
"portal": "数据分析",
"client": "连接配置",
"summary": "数据汇总",
"config": "配置",
"dashboard": "连接配置", "summary": "数据汇总",
"config": "配置", "schema": "概况",
"icons": {
"client": "https://img.icons8.com/officel/80/activity-grid.png",
"summary": "https://img.icons8.com/officel/80/activity-grid.png"
"dashboard": "https://img.icons8.com/officel/80/data-configuration.png",
"summary": "https://img.icons8.com/officel/80/statistics.png"
},
"input": {
"My Client": "我的数据",
"user_client_role": "用户角色",
"client_name": "数据名称",
"client_type": "数据类型",
"database": "数据库"
"My Dashboard": "我的数据",
"user_dashboard_role": "用户角色",
"dashboard_name": "数据名称",
"dashboard_type": "数据类型",
"database": "数据库",
"query": "条件",
"field": "字段",
"score": "排序"
},
"value": {
"user_client_role": {
"user_dashboard_role": {
"visitor": "访客",
"creator": "创建人",
"leader": "管理人员",
@ -26,13 +28,13 @@
"leader": "danger"
}
},
"client_type": {
"place": "place",
"dashboard_type": {
"place": "Place",
"mysql": "MySQL",
"redis": "Redis",
"icons": {
"mysql": "https://2023-contexts.shylinux.com/p/src/studio/studio.png?pod=20230511-mysql-story",
"redis": "https://2023-contexts.shylinux.com/p/src/client/redis.png?pod=20230511-redis-story"
"mysql": "/p/src/studio/studio.png?pod=20230511-mysql-story",
"redis": "/p/src/client/redis.png?pod=20230511-redis-story"
}
}
}

View File

@ -1,15 +1,7 @@
package dashboard
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"shylinux.com/x/ice"
"shylinux.com/x/icebergs/base/aaa"
"shylinux.com/x/icebergs/base/ctx"
"shylinux.com/x/icebergs/base/mdb"
"shylinux.com/x/icebergs/base/web"
kit "shylinux.com/x/toolkits"
"shylinux.com/x/operation/src/dashboard/model"
@ -17,46 +9,54 @@ import (
type summary struct {
Table
client client
create string `name:"create title* space index* query field score" role:"leader"`
modify string `name:"modify title* space index* query field score" role:"leader"`
dashboard dashboard
order string `data:"2"`
create string `name:"create space* index* query field title score" role:"leader"`
modify string `name:"modify space* index* query field title score" role:"leader"`
remove string `name:"remove" role:"leader"`
scan string `name:"scan" role:"leader"`
}
func (s summary) Scan(m *ice.Message, arg ...string) {
client_uid := kit.Select(m.Option(model.CLIENT_UID), arg, 0)
msg := m.Cmd(s.client, s.Select, model.UID, client_uid)
switch ClientType(kit.Int(msg.Append(model.TYPE))) {
case ClientPlace:
s.List(m, client_uid).Table(func(value ice.Maps) {
msg := s.SpaceCmd(m, value[web.SPACE], value[ctx.INDEX], mdb.SELECT, kit.Simple(kit.UnMarshal(value[model.QUERY])), kit.Dict(
mdb.SELECT, kit.Select("count(*)", value[mdb.FIELD]), ice.MSG_USERROLE, aaa.TECH,
))
s.Update(m, kit.Dict(model.VALUE, msg.Append(msg.Append(ice.MSG_APPEND))), model.UID, value[model.UID])
})
case ClientMySQL:
s.Open(m, func(db *gorm.DB) {
s.List(m, client_uid).Table(func(value ice.Maps) {
db = db.Table(s.TableName(kit.Join(kit.Slice(kit.Split(value[ctx.INDEX], "."), -2), ".")))
kit.If(value[model.QUERY], func(p string) { s.Where(m, db, kit.Simple(kit.UnMarshal(p))...) })
msg := s.Rows(m.Spawn(), db.Select(kit.Select("count(*)", value[mdb.FIELD])))
s.Update(m, kit.Dict(model.VALUE, msg.Append(msg.Append(ice.MSG_APPEND))), model.UID, value[model.UID])
})
})
}
dashboard_uid := kit.Select(m.Option(model.DASHBOARD_UID), arg, 0)
s.dashboard.Scan(s.List(m, dashboard_uid), dashboard_uid, func(msg *ice.Message, value ice.Maps) {
s.Update(m, kit.Dict(model.VALUE, msg.Append(msg.Append(ice.MSG_APPEND))), model.UID, value[model.UID])
})
}
func (s summary) List(m *ice.Message, arg ...string) *ice.Message {
if len(arg) == 0 {
return m
}
s.Orders(m, model.SCORE, s.Desc(model.CREATED_AT))
s.Select(m, model.CLIENT_UID, arg[0]).PushAction(s.Modify, s.Remove).Action(s.Create, s.Scan)
s.Select(m, model.DASHBOARD_UID, arg[0])
if s.IsLeader(m) {
m.PushAction(s.Modify, s.Remove).Action(s.Create, s.Scan, s.Schema)
} else {
m.Action()
}
return s.Button(m, "").Display("").DisplayCSS("")
}
func (s summary) Schema(m *ice.Message, arg ...string) {
databases := map[string]bool{}
tables := map[string]bool{}
stats := map[string]int{}
m.Cmd(s.dashboard, s.Schema).Table(func(value ice.Maps) {
if !databases[value["TABLE_SCHEMA"]] {
databases[value["TABLE_SCHEMA"]] = true
stats["TABLE_SCHEMA"]++
}
if !tables[value["TABLE_NAME"]] {
tables[value["TABLE_NAME"]] = true
stats["TABLE_NAME"]++
}
stats["TABLE_ROWS"] += kit.Int(value["TABLE_ROWS"])
stats["DATA_LENGTH"] += kit.Int(value["DATA_LENGTH"])
stats["INDEX_LENGTH"] += kit.Int(value["INDEX_LENGTH"])
})
for k, v := range stats {
m.Push(model.TITLE, k).Push(model.VALUE, kit.TrimSuffix(kit.FmtSize(v), "B"))
}
m.Display("").DisplayCSS("")
}
func init() { ice.TeamCtxCmd(summary{}) }
func (s summary) Open(msg *ice.Message, cb func(*gorm.DB)) {
dsn := kit.Format("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True", msg.Append(model.USERNAME), msg.Append(model.PASSWORD), msg.Append(model.HOST), msg.Append(model.PORT), msg.Append(model.DATABASE))
if db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)}); !msg.Warn(err) {
cb(db)
}
}

View File

@ -1,12 +1,15 @@
Volcanos(chat.ONIMPORT, {
_init: function(can, msg) { var count = (parseInt(can.ConfWidth()/80)); if (msg.Length() < count) { count = msg.Length() }
var width = can.ConfWidth()/(count||1)
_init: function(can, msg) { var width = can.onimport.width(can, 80, msg.Length())
can.page.Append(can, can._output, msg.Table(function(value) {
return {view: html.ITEM, style: {width: width}, list: [
{view: [html.VALUE, "", value.value]}, {view: [html.TITLE, "", value.title]},
{view: [html.VALUE, "", can.base.trimSuffix(can.base.Size(value.value), "B")]}, {view: [html.TITLE, "", value.title||value.index.split(".").pop()]},
], oncontextmenu: function(event) {
can.user.carteItem(event, can, value)
can.onkeymap.prevent(event), can.user.carteItem(event, can, value)
}}
}))
},
width: function(can, min, length) {
var count = (parseInt(can.ConfWidth()/min)); if (length < count) { count = length }
return can.ConfWidth()/(count||1)
},
})

View File

@ -1,15 +0,0 @@
package dashboard
import (
"shylinux.com/x/ice"
"shylinux.com/x/operation/src/operation"
)
type userClient struct{ Table }
func init() { ice.TeamCtxCmd(userClient{}) }
type UserClientRole = operation.UserCloudRole
var UserClientRoleList = operation.UserCloudRoleList

View File

@ -7,6 +7,7 @@ import (
_ "shylinux.com/x/operation/src/development"
_ "shylinux.com/x/operation/src/operation"
_ "shylinux.com/x/operation/src/production"
_ "shylinux.com/x/operation/src/storage"
)
func main() { print(ice.Run()) }

View File

@ -39,6 +39,7 @@ func (s Table) RewriteAppend(m *ice.Message, arg ...string) *ice.Message {
func (s Table) CheckRole(m *ice.Message, arg ...string) *ice.Message {
role := UserCloudRole(s.UserPlaceRole(m))
m.WarnNotRight(!kit.IsIn(role.String(), append(arg, UserCloudCreator.String())...), role.String())
m.Option(model.USER_ROLE, kit.Format(role))
return m
}

View File

@ -10,6 +10,7 @@ const (
TITLE = "title"
CONTENT = "content"
USER_UID = "user_uid"
USER_ROLE = "user_role"
USER_CLOUD_ROLE = "user_cloud_role"
CLOUD_UID = "cloud_uid"
CLOUD_NAME = "cloud_name"

View File

@ -21,10 +21,10 @@ type Case struct {
func (s Case) Create(m *ice.Message, arg ...string) {
s.ValueCreate(m, arg...)
s.GetCommandUID(m)
s.SendMessage(m, "", "")
s.taskCount(m, "1")
s.planCount(m, "1")
s.GetCommandUID(m)
s.SendMessage(m, "", "")
}
func (s Case) Remove(m *ice.Message, arg ...string) {
s.ValueRemove(m, arg...)
@ -74,10 +74,7 @@ func (s Case) planCount(m *ice.Message, value string) {
s.Table.planCount(m, model.CASE_COUNT, value)
}
func (s Case) taskCount(m *ice.Message, value string) {
if m.IsErr() {
return
}
if m.Option(model.TASK_UID) == "" {
if m.IsErr() || m.Option(model.TASK_UID) == "" {
return
}
m.Cmd(task{}, s.AddCount, model.CASE_COUNT, value, m.Option(model.TASK_UID))

View File

@ -3,7 +3,10 @@ Volcanos(chat.ONIMPORT, {
can.onimport.myView(can, msg, function(value) { return [
{view: html.TITLE, list: [value.title, can.onimport.textView(can, value), can.onimport.titleAction(can, value)]},
{view: html.STATUS, list: [value.uid.slice(0, 6), can.onimport.timeView(can, value), value.user_name]},
{view: html.STATUS, list: [can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time), "计划:", value.plan_title]},
{view: html.STATUS, list: [
can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time),
"计划:", value.plan_title,
]},
{view: html.OUTPUT, list: [value.content]},
] })
},

View File

@ -63,10 +63,7 @@ func (s Table) ChangeStatus(m *ice.Message, from, to int, arg ...string) {
s.DashboardUpdate(m)
}
func (s Table) planCount(m *ice.Message, key, value string) {
if m.IsErr() {
return
}
if m.Option(model.PLAN_UID) == "" {
if m.IsErr() || m.Option(model.PLAN_UID) == "" {
return
}
m.Cmd(plan{}, s.AddCount, key, value, m.Option(model.PLAN_UID))

View File

@ -25,15 +25,15 @@ type issue struct {
func (s issue) Create(m *ice.Message, arg ...string) {
s.ValueCreate(m, kit.ArgDef(arg, kit.Simple(model.ISSUE_TYPE, IssueFeature, model.LEVEL, Level3)...)...)
s.planCount(m, "1")
s.DashboardUpdate(m)
s.GetCommandUID(m)
s.SendMessage(m, "", "")
s.DashboardUpdate(m)
s.planCount(m, "1")
}
func (s issue) Remove(m *ice.Message, arg ...string) {
s.ValueRemove(m, arg...)
s.DashboardUpdate(m)
s.planCount(m, "-1")
s.DashboardUpdate(m)
}
func (s issue) List(m *ice.Message, arg ...string) {
isLeader, user_uid := s.IsLeader(m), m.Option(model.USER_UID)

View File

@ -7,7 +7,10 @@ Volcanos(chat.ONIMPORT, {
can.onimport.textView(can, value), can.onimport.titleAction(can, value),
]},
{view: html.STATUS, list: [value.uid.slice(0, 6), can.onimport.timeView(can, value), value.user_name]},
{view: html.STATUS, list: [can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time), "计划:", value.plan_title, "任务:", value.task_count+" 个"]},
{view: html.STATUS, list: [
can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time),
"计划:", value.plan_title, "任务:", value.task_count+" 个",
]},
{view: html.OUTPUT, list: [value.content]},
] })
},

View File

@ -31,7 +31,7 @@ func (s plan) List(m *ice.Message, arg ...string) {
switch PlanStatus(kit.Int(value[model.PLAN_STATUS])) {
case PlanCreate:
if isLeader {
button = append(button, s.Modify, s.Process)
button = append(button, s.Process, s.Modify, s.Remove)
}
case PlanProcess:
button = append(button, s.IssueCreate)

View File

@ -4,7 +4,10 @@ Volcanos(chat.ONIMPORT, {
{view: html.TITLE, list: [value.title, can.onimport.textView(can, value), can.onimport.titleAction(can, value)]},
{view: html.STATUS, list: [value.uid.slice(0, 6), can.onimport.timeView(can, value), value.user_name]},
{view: html.STATUS, list: [value.begin_time.split(" ")[0], "~", value.end_time.split(" ")[0]]},
{view: html.STATUS, list: [can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time), "需求:", value.issue_count+" 个", "任务:", value.task_count+" 个", "用例:", value.case_count+" 个"]},
{view: html.STATUS, list: [
can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time),
"需求:", value.issue_count+" 个", "任务:", value.task_count+" 个", "用例:", value.case_count+" 个",
]},
{view: html.OUTPUT, list: [value.content]},
] })
},

View File

@ -14,12 +14,11 @@ type Portal struct {
}
func (s Portal) AfterPlaceAuth(m *ice.Message, arg ...string) {
s.DashboardCreate(m, "")
s.DashboardInsert(m, "1", "需求总量", issue{}, "", model.STORY_UID, m.Option(model.STORY_UID))
defer s.DashboardCreate(m, "")()
s.DashboardInsert(m, "1", "需求总量", issue{}, "")
s.DashboardInsert(m, "2", "需求待办", issue{}, "", "story_uid = ? AND status != ? AND status != ?", m.Option(model.STORY_UID), IssueRejected, IssueFinish)
s.DashboardInsert(m, "3", "任务总量", task{}, "", model.STORY_UID, m.Option(model.STORY_UID))
s.DashboardInsert(m, "3", "任务总量", task{}, "")
s.DashboardInsert(m, "4", "任务待办", task{}, "", "story_uid = ? AND status != ?", m.Option(model.STORY_UID), TaskFinish)
s.DashboardUpdate(m)
}
func init() { gonganxitong.PortalCmd(Portal{Portal: guanlixitong.NewPortal(userStory{}, story{})}) }

View File

@ -1,15 +1,8 @@
{
"portal": "产品迭代",
"plan": "迭代计划",
"issue": "产品需求",
"task": "开发任务",
"case": "测试用例",
"member": "项目成员",
"process": "开始",
"planBind": "绑定计划",
"issueCreate": "创建需求",
"taskCreate": "创建任务",
"caseCreate": "创建用例",
"portal": "产品迭代", "member": "项目成员",
"plan": "迭代计划", "issue": "产品需求", "task": "开发任务", "case": "测试用例",
"planBind": "绑定计划", "process": "开始",
"issueCreate": "创建需求", "taskCreate": "创建任务", "caseCreate": "创建用例",
"style": {
"process": "notice",
"finish": "danger",
@ -79,18 +72,26 @@
"process": "开发中",
"finish": "已完成",
"style": {
"create": "danger",
"approved": "danger",
"rejected": "danger"
}
},
"task_status": {
"create": "待开发",
"process": "开发中",
"finish": "已完成"
"finish": "已完成",
"style": {
"create": "danger"
}
},
"case_status": {
"create": "待测试",
"process": "测试中",
"finish": "已完成"
"finish": "已完成",
"style": {
"create": "danger"
}
},
"level": {
"level-1": "紧急",

View File

@ -23,17 +23,17 @@ type task struct {
func (s task) Create(m *ice.Message, arg ...string) {
s.ValueCreate(m, kit.ArgDef(arg, kit.Simple(model.BEGIN_TIME, m.Time(), model.END_TIME, m.Time("72h"))...)...)
s.GetCommandUID(m)
s.SendMessage(m, "", "")
s.DashboardUpdate(m)
s.issueCount(m, "1")
s.planCount(m, "1")
s.DashboardUpdate(m)
s.GetCommandUID(m)
s.SendMessage(m, "", "")
}
func (s task) Remove(m *ice.Message, arg ...string) {
s.ValueRemove(m, arg...)
s.DashboardUpdate(m)
s.issueCount(m, "-1")
s.planCount(m, "-1")
s.DashboardUpdate(m)
}
func (s task) List(m *ice.Message, arg ...string) {
isLeader, user_uid := s.IsLeader(m), m.Option(model.USER_UID)
@ -87,10 +87,7 @@ func (s task) planCount(m *ice.Message, value string) {
s.Table.planCount(m, model.TASK_COUNT, value)
}
func (s task) issueCount(m *ice.Message, value string) {
if m.IsErr() {
return
}
if m.Option(model.ISSUE_UID) == "" {
if m.IsErr() || m.Option(model.ISSUE_UID) == "" {
return
}
m.Cmd(issue{}, s.AddCount, model.TASK_COUNT, value, m.Option(model.ISSUE_UID))

View File

@ -3,7 +3,10 @@ Volcanos(chat.ONIMPORT, {
can.onimport.myView(can, msg, function(value) { return [
{view: html.TITLE, list: [value.title, can.onimport.textView(can, value), can.onimport.titleAction(can, value)]},
{view: html.STATUS, list: [value.uid.slice(0, 6), can.onimport.timeView(can, value), value.user_name]},
{view: html.STATUS, list: [can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time), "计划:", value.plan_title, "用例:", value.case_count+" 个"]},
{view: html.STATUS, list: [
can.base.TimeTrim(value.process_time), "~", can.base.TimeTrim(value.finish_time),
"计划:", value.plan_title, "用例:", value.case_count+" 个",
]},
{view: html.OUTPUT, list: [value.content]},
] })
},

View File

@ -13,7 +13,5 @@ func init() { ice.TeamCtxCmd(userStory{}) }
type UserStoryRole = operation.UserCloudRole
var UserStoryCreator = operation.UserCloudCreator
var UserStoryLeader = operation.UserCloudLeader
var UserStoryRoleList = operation.UserCloudRoleList