├── README.md ├── config.json ├── images ├── 1 ├── aaaa1.png ├── aaaa2.png ├── aaaa3.png ├── init1.png ├── init2.png └── init3.png ├── logo.png ├── main.go └── up.go /README.md: -------------------------------------------------------------------------------- 1 | # macbox 2 | 3 |

4 |
5 |
6 | GitHub stars 7 | GitHub releases 8 | License 9 | Downloads 10 |
11 |
12 |
13 | 下载程序 14 | · 15 | 反馈Bug|提交需求 16 | 17 |

18 | 19 | ## 使用 20 | 21 | 第一步,下载这个项目到本地,解压 22 | 23 | 第二步,将文件夹 macbox-main 重命名成macbox 24 | 25 | 第三步,初始化环境 26 | `go mod init macbox` 27 | ![](images/init1.png) 28 | 29 | 第四步,下载依赖 30 | `go mod tidy` 31 | ![](images/init2.png) 32 | 33 | 第五步,编译并执行 34 | `go build main.go` 35 | ![](images/init3.png) 36 | 37 | 第六步,修改你的配置文件 38 | 39 | 1、使用编辑器直接编辑config.json 40 | 41 | 2、`go build up.go` 之后使用up工具去管理config.json 42 | 43 | 44 | ## 效果图1 45 | 46 | ![](images/aaaa1.png) 47 | 48 | ## 效果图2 49 | 50 | ![](images/aaaa2.png) 51 | 52 | 53 | 54 | ![](images/aaaa3.png) 55 | 有建议和需求可提 ISSUE 56 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "lists": [ 3 | { 4 | "name": "信息收集", 5 | "item": [ 6 | { 7 | "item_name": "OneForAll", 8 | "item_des": "子域名、端口扫描等", 9 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/OneForAll\", \"ls\"" 10 | }, 11 | { 12 | "item_name": "fofaView", 13 | "item_des": "fofa空间测绘", 14 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/01_Information_Gathering/FofaViewer_1.1.15_JDK8/fofaviewer.jar" 15 | }, 16 | { 17 | "item_name": "flashsearch", 18 | "item_des": "聚合搜索", 19 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/01_Information_Gathering/flashsearch2.0/FlashSearch.jar" 20 | }, 21 | { 22 | "item_name": "Finger", 23 | "item_des": "指纹识别", 24 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/Finger-main\", \"ls\"" 25 | }, 26 | { 27 | "item_name": "Packer-Fuzzer", 28 | "item_des": "一款针对Webpack等前端打包工具所构造的网站进行快速、高效安全检测的扫描工具", 29 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/Packer-Fuzzer-master\", \"ls\"" 30 | }, 31 | { 32 | "item_name": "dirsearch-0.4.3", 33 | "item_des": "dirsearch-0.4.3", 34 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/dirsearch-0.4.3\", \"ls\"" 35 | }, 36 | { 37 | "item_name": "dirsearch_bypass403", 38 | "item_des": "", 39 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/dirsearch_bypass403\", \"ls\"" 40 | }, 41 | { 42 | "item_name": "URLFinder_Darwin_x86_64", 43 | "item_des": "一款快速、全面、易用的页面信息提取工具", 44 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/01_Information_Gathering/URLFinder_Darwin_x86_64\", \"ls\"" 45 | }, 46 | { 47 | "item_name": "lzScan", 48 | "item_des": "", 49 | "item_run": "\"./tools/01_Information_Gathering/lzScan\", \"./lzscan\"" 50 | } 51 | ] 52 | }, 53 | { 54 | "name": "shell管理", 55 | "item": [ 56 | { 57 | "item_name": "AntSword", 58 | "item_des": "中国蚁剑", 59 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/02_WebShell/AntSword.App" 60 | }, 61 | { 62 | "item_name": "Behinder", 63 | "item_des": "", 64 | "item_run": "", 65 | "sub_items": [ 66 | { 67 | "item_name": "Behinder_v4.1", 68 | "item_des": "", 69 | "item_run": "\"/Users/xxx/Desktop/attackBOX/tools/02_WebShell/Behinder/Behinder_v4.1/\", \"/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar Behinder.jar\"" 70 | } 71 | ] 72 | }, 73 | { 74 | "item_name": "Godzilla", 75 | "item_des": "", 76 | "item_run": "open ./tools/02_WebShell/Godzilla.app" 77 | }, 78 | { 79 | "item_name": "TianXie", 80 | "item_des": "", 81 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/02_WebShell/TianXie/TianXie.jar" 82 | }, 83 | { 84 | "item_name": "Webshell_Generate-1.2.1", 85 | "item_des": "", 86 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/02_WebShell/Webshell_Generate-1.2.1.jar" 87 | }, 88 | { 89 | "item_name": "Cknife", 90 | "item_des": "", 91 | "item_run": "open ./tools/02_WebShell/Cknife.app" 92 | }, 93 | { 94 | "item_name": "webshell", 95 | "item_des": "", 96 | "item_run": "open ./tools/02_WebShell/webshell" 97 | } 98 | ] 99 | }, 100 | { 101 | "name": "抓包代理", 102 | "item": [ 103 | { 104 | "item_name": "Proxifier", 105 | "item_des": "", 106 | "item_run": "open ./tools/03_Proxies/Proxifier.app" 107 | }, 108 | { 109 | "item_name": "BurpSuite_old", 110 | "item_des": "", 111 | "item_run": "open ./tools/03_Proxies/BurpSuiteOld.app" 112 | }, 113 | { 114 | "item_name": "BurpSuite_new", 115 | "item_des": "", 116 | "item_run": "open ./tools/03_Proxies/BurpSuiteNew.app" 117 | }, 118 | { 119 | "item_name": "Yakit", 120 | "item_des": "", 121 | "item_run": "open ./tools/03_Proxies/Yakit.app" 122 | } 123 | ] 124 | }, 125 | { 126 | "name": "漏洞利用", 127 | "item": [ 128 | { 129 | "item_name": "sqlmap", 130 | "item_des": "", 131 | "item_run": "\"./tools/04_vulnAttack/sqlmap-master\", \"ls\"" 132 | }, 133 | { 134 | "item_name": "云资产利用工具", 135 | "item_des": "", 136 | "item_run": "./tools/04_vulnAttack/云资产利用工具", 137 | "sub_items": [ 138 | { 139 | "item_name": "oss-browser", 140 | "item_des": "", 141 | "item_run": "open ./tools/04_vulnAttack/云资产利用工具/oss-browser-darwin-x64/oss-browser.app" 142 | }, 143 | { 144 | "item_name": "cf_v0.5.0", 145 | "item_des": "", 146 | "item_run": "\"./tools/04_vulnAttack/云资产利用工具/cf_v0.5.0_darwin_amd64\", \"ls\"" 147 | } 148 | ] 149 | }, 150 | { 151 | "item_name": "信息泄露利用工具", 152 | "item_des": "", 153 | "item_run": "./tools/04_vulnAttack/信息泄露利用工具" 154 | }, 155 | { 156 | "item_name": "Shiro反序列化漏洞综合利用工具", 157 | "item_des": "", 158 | "item_run": "./tools/04_vulnAttack/Shiro反序列化漏洞综合利用工具" 159 | }, 160 | { 161 | "item_name": "Spring综合漏洞利用工具", 162 | "item_des": "", 163 | "item_run": "./tools/04_vulnAttack/Spring综合漏洞利用工具" 164 | }, 165 | { 166 | "item_name": "Geoserver漏洞利用工具", 167 | "item_des": "", 168 | "item_run": "./tools/04_vulnAttack/Geoserver漏洞利用工具/GeoServer漏洞综合利用工具v1.2_内部版" 169 | }, 170 | { 171 | "item_name": "Nacos综合漏洞利用工具", 172 | "item_des": "", 173 | "item_run": "./tools/04_vulnAttack/Nacos综合漏洞利用工具" 174 | }, 175 | { 176 | "item_name": "大华漏洞综合利用工具", 177 | "item_des": "", 178 | "item_run": "./tools/04_vulnAttack/大华Dahua综合漏洞利用工具" 179 | }, 180 | { 181 | "item_name": "Spring综合漏洞利用工具", 182 | "item_des": "", 183 | "item_run": "./tools/04_vulnAttack/Spring综合漏洞利用工具" 184 | }, 185 | { 186 | "item_name": "Hikvision综合漏洞利用工具", 187 | "item_des": "", 188 | "item_run": "./tools/04_vulnAttack/Hikvision综合漏洞利用工具" 189 | }, 190 | { 191 | "item_name": "OA类漏洞", 192 | "item_des": "", 193 | "item_run": "./tools/04_vulnAttack/OA类漏洞", 194 | "sub_items": [ 195 | { 196 | "item_name": "通达", 197 | "item_des": "", 198 | "item_run": "./tools/04_vulnAttack/OA类漏洞/通达 " 199 | }, 200 | { 201 | "item_name": "YongYou", 202 | "item_des": "", 203 | "item_run": "./tools/04_vulnAttack/OA类漏洞/YongYou" 204 | }, 205 | { 206 | "item_name": "综合工具", 207 | "item_des": "", 208 | "item_run": "./tools/04_vulnAttack/OA类漏洞/综合" 209 | } 210 | ] 211 | }, 212 | { 213 | "item_name": "框架中间件", 214 | "item_des": "", 215 | "item_run": "./tools/04_vulnAttack/框架中间件" 216 | }, 217 | { 218 | "item_name": "综合漏洞利用工具", 219 | "item_des": "", 220 | "item_run": "./tools/04_vulnAttack/综合漏洞利用工具", 221 | "sub_items": [ 222 | { 223 | "item_name": "0x7eTeamTools", 224 | "item_des": "", 225 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/04_vulnAttack/综合漏洞利用工具/0x7eTeamTools.v1.2/0x7eTeamTools_v1.2.jar" 226 | }, 227 | { 228 | "item_name": "LiqunKit_1.6.2_交流版", 229 | "item_des": "", 230 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/04_vulnAttack/综合漏洞利用工具/LiqunKit_1.5.1/LiqunKit_1.6.2_交流版.jar" 231 | }, 232 | { 233 | "item_name": "Apt_tools", 234 | "item_des": "", 235 | "item_run": "/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -jar ./tools/04_vulnAttack/综合漏洞利用工具/apt/apt_tools-jar-with-dependencies.jar" 236 | } 237 | ] 238 | }, 239 | { 240 | "item_name": "数据库综合利用工具", 241 | "item_des": "", 242 | "item_run": "./tools/04_vulnAttack/数据库综合利用工具" 243 | } 244 | ] 245 | }, 246 | { 247 | "name": "漏洞扫描", 248 | "item": [ 249 | { 250 | "item_name": "xray_1.9.10", 251 | "item_des": "", 252 | "item_run": "\"./tools/05_vulnScan/xray_1.9.10\", \"ls\"" 253 | }, 254 | { 255 | "item_name": "nuclei", 256 | "item_des": "", 257 | "item_run": "\"./tools/05_vulnScan/nuclei\", \"ls\"" 258 | }, 259 | { 260 | "item_name": "Goby", 261 | "item_des": "", 262 | "item_run": "open ./tools/05_vulnScan/Goby.app" 263 | }, 264 | { 265 | "item_name": "afrog", 266 | "item_des": "", 267 | "item_run": "\"./tools/05_vulnScan/afrog\", \"ls\"" 268 | } 269 | ] 270 | }, 271 | { 272 | "name": "通信隧道", 273 | "item": [ 274 | { 275 | "item_name": "Stowaway", 276 | "item_des": "", 277 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/06_tunnel/Stowaway" 278 | }, 279 | { 280 | "item_name": "suo5", 281 | "item_des": "", 282 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/06_tunnel/suo5" 283 | }, 284 | { 285 | "item_name": "Neo-reGeorg-5.2.0", 286 | "item_des": "", 287 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/06_tunnel/Neo-reGeorg-5.2.0" 288 | }, 289 | { 290 | "item_name": "FRP", 291 | "item_des": "", 292 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/06_tunnel/FRP" 293 | } 294 | ] 295 | }, 296 | { 297 | "name": "后渗透", 298 | "item": [ 299 | { 300 | "item_name": "NetExec", 301 | "item_des": "NetExec", 302 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/07_PostHacking/NetExec" 303 | }, 304 | { 305 | "item_name": "Fscan", 306 | "item_des": "Fscan", 307 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/07_PostHacking/Fscan" 308 | }, 309 | { 310 | "item_name": "CobaltStrike", 311 | "item_des": "cobaltstrike", 312 | "item_run": "open /Users/xxx/Desktop/attackBOX/tools/07_PostHacking/cobaltstrike", 313 | "sub_items": [ 314 | { 315 | "item_name": "CobaltStrike4.9.1", 316 | "item_des": "CobaltStrike4.9.1", 317 | "item_run": "java -Dfile.encoding=UTF-8 -XX:ParallelGCThreads=4 -XX:+AggressiveHeap -XX:+UseParallelGC -Xms512M -Xmx1024M -jar /Users/xxx/Desktop/attackBOX/tools/07_PostHacking/cobaltstrike/cobaltstrike4.9.1/CobaltStrike4.9.1_Client/cobaltstrike-client.jar" 318 | } 319 | ] 320 | } 321 | ] 322 | } 323 | ], 324 | "About": {} 325 | } 326 | -------------------------------------------------------------------------------- /images/1: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/aaaa1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/aaaa1.png -------------------------------------------------------------------------------- /images/aaaa2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/aaaa2.png -------------------------------------------------------------------------------- /images/aaaa3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/aaaa3.png -------------------------------------------------------------------------------- /images/init1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/init1.png -------------------------------------------------------------------------------- /images/init2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/init2.png -------------------------------------------------------------------------------- /images/init3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/images/init3.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x7eTeam/macbox/c1fce0640b27634486feabfca405a93f1c85ab1d/logo.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "fyne.io/systray" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | type Item struct { 16 | ItemName string `json:"item_name"` 17 | ItemDes string `json:"item_des"` 18 | ItemRun string `json:"item_run"` 19 | SubItems []Item `json:"sub_items,omitempty"` 20 | } 21 | 22 | type List struct { 23 | Name string `json:"name"` 24 | Item []Item `json:"item"` 25 | } 26 | 27 | type About struct { 28 | // 如果有字段可以在这里定义 29 | } 30 | 31 | type Data struct { 32 | Lists []List `json:"lists"` 33 | About About `json:"about"` 34 | } 35 | 36 | var ( 37 | data Data 38 | dataLock sync.RWMutex 39 | dynamicMenuItems []*systray.MenuItem 40 | initialWorkDir string // 新增:用于存储初始工作目录 41 | ) 42 | 43 | func main() { 44 | var err error 45 | initialWorkDir, err = os.Getwd() 46 | if err != nil { 47 | log.Printf("Error getting initial working directory: %v", err) 48 | initialWorkDir = "" // 如果获取失败,设置为空字符串 49 | } 50 | systray.Run(onReady, nil) 51 | } 52 | 53 | func getConfig(filename string) (Data, error) { 54 | file, err := os.Open(filename) 55 | if err != nil { 56 | return Data{}, err 57 | } 58 | defer file.Close() 59 | 60 | dataBytes, err := os.ReadFile(filename) 61 | if err != nil { 62 | return Data{}, err 63 | } 64 | 65 | var data Data 66 | if err := json.Unmarshal(dataBytes, &data); err != nil { 67 | return Data{}, err 68 | } 69 | return data, nil 70 | } 71 | 72 | func onReady() { 73 | icon, err := os.ReadFile("logo.png") 74 | if err != nil { 75 | systray.SetTitle("MACBOX") 76 | } else { 77 | systray.SetIcon(icon) 78 | } 79 | 80 | systray.SetTooltip("一款mac下的标签栏工具") 81 | 82 | // 创建固定菜单项 83 | mIndex := systray.AddMenuItem("About", "") 84 | systray.AddSeparator() 85 | 86 | mReload := systray.AddMenuItem("配置重载", "") 87 | mEdit := systray.AddMenuItem("编辑选项", "") 88 | mCheckUpdate := systray.AddMenuItem("检查更新", "") 89 | mExit := systray.AddMenuItem("退出", "") 90 | 91 | systray.AddSeparator() 92 | // 初始加载配置 93 | loadAndUpdateMenu() 94 | 95 | go func() { 96 | for { 97 | select { 98 | case <-mIndex.ClickedCh: 99 | openUrl("https://github.com/0x7eTeam/macbox") 100 | case <-mEdit.ClickedCh: 101 | executeCommand("./up") 102 | case <-mReload.ClickedCh: 103 | loadAndUpdateMenu() 104 | case <-mCheckUpdate.ClickedCh: 105 | //防止外链报毒,这里不写了。。。 106 | mCheckUpdate.SetTitle("已是最新版本") 107 | mCheckUpdate.Disable() 108 | case <-mExit.ClickedCh: 109 | systray.Quit() 110 | } 111 | } 112 | }() 113 | } 114 | 115 | func loadAndUpdateMenu() { 116 | var err error 117 | dataLock.Lock() 118 | data, err = getConfig("config.json") 119 | dataLock.Unlock() 120 | 121 | if err != nil { 122 | log.Printf("Error loading config: %s", err) 123 | return 124 | } 125 | 126 | // 清除动态菜单项 127 | clearDynamicMenuItems() 128 | 129 | // 添加动态菜单项 130 | dataLock.RLock() 131 | for _, list := range data.Lists { 132 | var1 := systray.AddMenuItem(list.Name, "") 133 | dynamicMenuItems = append(dynamicMenuItems, var1) 134 | for _, item := range list.Item { 135 | addMenuItem(var1, item) 136 | } 137 | } 138 | dataLock.RUnlock() 139 | } 140 | 141 | func clearDynamicMenuItems() { 142 | for _, item := range dynamicMenuItems { 143 | item.Hide() 144 | } 145 | dynamicMenuItems = nil 146 | } 147 | 148 | func addMenuItem(parent *systray.MenuItem, item Item) { 149 | if len(item.SubItems) > 0 { 150 | // 如果有子项,创建子菜单 151 | subMenu := parent.AddSubMenuItem(item.ItemName, item.ItemDes) 152 | dynamicMenuItems = append(dynamicMenuItems, subMenu) 153 | for _, subItem := range item.SubItems { 154 | addMenuItem(subMenu, subItem) 155 | } 156 | } else { 157 | // 如果没有子项,创建普通菜单项 158 | itemMenu := parent.AddSubMenuItem(item.ItemName, item.ItemDes) 159 | dynamicMenuItems = append(dynamicMenuItems, itemMenu) 160 | go func(runCommand string) { 161 | for range itemMenu.ClickedCh { 162 | fmt.Printf("Executing command: %s\n", runCommand) 163 | executeCommand(runCommand) 164 | } 165 | }(item.ItemRun) 166 | } 167 | parent.AddSeparator() 168 | } 169 | 170 | func openUrl(url string) error { 171 | var cmd string 172 | var args []string 173 | 174 | switch runtime.GOOS { 175 | case "windows": 176 | cmd = "cmd" 177 | args = []string{"/c", "start"} 178 | case "darwin": 179 | cmd = "open" 180 | default: // "linux", "freebsd", "openbsd", "netbsd" 181 | cmd = "xdg-open" 182 | } 183 | args = append(args, url) 184 | return exec.Command(cmd, args...).Start() 185 | } 186 | 187 | func executeCommand(itemRun string) error { 188 | parts := strings.SplitN(itemRun, ", ", 2) 189 | 190 | if len(parts) == 1 { 191 | // 检查是否是目录 192 | fileInfo, err := os.Stat(parts[0]) 193 | if err == nil && fileInfo.IsDir() { 194 | // 是目录,打开它 195 | return openDirectory(parts[0]) 196 | } 197 | // 不是目录,执行命令 198 | cmdParts := strings.Fields(parts[0]) 199 | cmd := exec.Command(cmdParts[0], cmdParts[1:]...) 200 | cmd.Stdout = os.Stdout 201 | cmd.Stderr = os.Stderr 202 | return cmd.Start() // 使用 Start() 而不是 Run() 来异步执行 203 | } else if len(parts) == 2 { 204 | // 在新终端中执行命令 205 | directory := strings.Trim(parts[0], "\"") 206 | command := strings.Trim(parts[1], "\"") 207 | 208 | if initialWorkDir == "" { 209 | return fmt.Errorf("初始工作目录未知") 210 | } 211 | 212 | script := fmt.Sprintf(` 213 | tell application "Terminal" 214 | do script "cd %s && clear && %s && %s" 215 | activate 216 | end tell 217 | `, initialWorkDir, directory, command) 218 | 219 | cmd := exec.Command("osascript", "-e", script) 220 | return cmd.Start() // 使用 Start() 而不是 Run() 来异步执行 221 | } else { 222 | return fmt.Errorf("无效的命令格式") 223 | } 224 | } 225 | func openDirectory(path string) error { 226 | var cmd *exec.Cmd 227 | switch runtime.GOOS { 228 | case "darwin": 229 | cmd = exec.Command("open", path) 230 | case "windows": 231 | cmd = exec.Command("explorer", path) 232 | default: // Linux and other Unix-like systems 233 | cmd = exec.Command("xdg-open", path) 234 | } 235 | return cmd.Start() 236 | } 237 | -------------------------------------------------------------------------------- /up.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | 8 | "fyne.io/fyne/v2" 9 | "fyne.io/fyne/v2/app" 10 | "fyne.io/fyne/v2/container" 11 | "fyne.io/fyne/v2/dialog" 12 | "fyne.io/fyne/v2/widget" 13 | ) 14 | 15 | type Config struct { 16 | Lists []List `json:"lists"` 17 | About struct{} 18 | } 19 | 20 | type List struct { 21 | Name string `json:"name"` 22 | Item []Item `json:"item"` 23 | } 24 | 25 | type Item struct { 26 | ItemName string `json:"item_name"` 27 | ItemDes string `json:"item_des"` 28 | ItemRun string `json:"item_run"` 29 | SubItems []SubItem `json:"sub_items,omitempty"` 30 | } 31 | 32 | type SubItem struct { 33 | ItemName string `json:"item_name"` 34 | ItemDes string `json:"item_des"` 35 | ItemRun string `json:"item_run"` 36 | } 37 | 38 | var config Config 39 | 40 | func main() { 41 | a := app.New() 42 | w := a.NewWindow("Config Editor") 43 | 44 | loadConfig() 45 | 46 | listView := createListView(w) 47 | detailView := container.NewVBox() 48 | 49 | split := container.NewHSplit(listView, detailView) 50 | split.Offset = 0.3 51 | 52 | w.SetContent(split) 53 | w.Resize(fyne.NewSize(800, 600)) 54 | w.ShowAndRun() 55 | } 56 | 57 | func createListView(w fyne.Window) *fyne.Container { 58 | list := widget.NewList( 59 | func() int { return len(config.Lists) }, 60 | func() fyne.CanvasObject { 61 | return widget.NewLabel("Template") 62 | }, 63 | func(id widget.ListItemID, item fyne.CanvasObject) { 64 | item.(*widget.Label).SetText(config.Lists[id].Name) 65 | }, 66 | ) 67 | 68 | list.OnSelected = func(id widget.ListItemID) { 69 | showListDetails(w, id) 70 | } 71 | 72 | addButton := widget.NewButton("Add List", func() { 73 | addNewList(w) 74 | }) 75 | 76 | return container.NewBorder(nil, addButton, nil, nil, list) 77 | } 78 | 79 | func showListDetails(w fyne.Window, id widget.ListItemID) { 80 | list := &config.Lists[id] 81 | 82 | nameEntry := widget.NewEntry() 83 | nameEntry.SetText(list.Name) 84 | 85 | itemList := widget.NewList( 86 | func() int { return len(list.Item) }, 87 | func() fyne.CanvasObject { 88 | return widget.NewLabel("Template") 89 | }, 90 | func(id widget.ListItemID, item fyne.CanvasObject) { 91 | item.(*widget.Label).SetText(list.Item[id].ItemName) 92 | }, 93 | ) 94 | 95 | itemList.OnSelected = func(itemID widget.ListItemID) { 96 | showItemDetails(w, id, itemID) 97 | } 98 | 99 | addItemButton := widget.NewButton("Add Item", func() { 100 | addNewItem(w, id) 101 | }) 102 | 103 | saveButton := widget.NewButton("Save", func() { 104 | list.Name = nameEntry.Text 105 | saveConfig() 106 | refreshUI(w) 107 | }) 108 | 109 | deleteButton := widget.NewButton("Delete List", func() { 110 | dialog.ShowConfirm("Delete List", "Are you sure you want to delete this list?", func(b bool) { 111 | if b { 112 | config.Lists = append(config.Lists[:id], config.Lists[id+1:]...) 113 | saveConfig() 114 | showListDetails(w, 0) // Show first list after deletion 115 | refreshUI(w) 116 | } 117 | }, w) 118 | }) 119 | 120 | content := container.NewBorder( 121 | container.NewVBox(nameEntry, saveButton, deleteButton), 122 | addItemButton, 123 | nil, 124 | nil, 125 | itemList, 126 | ) 127 | 128 | updateDetailView(w, content) 129 | refreshUI(w) 130 | } 131 | 132 | func showItemDetails(w fyne.Window, listID, itemID widget.ListItemID) { 133 | item := &config.Lists[listID].Item[itemID] 134 | 135 | nameEntry := widget.NewEntry() 136 | nameEntry.SetText(item.ItemName) 137 | 138 | desEntry := widget.NewMultiLineEntry() 139 | desEntry.SetText(item.ItemDes) 140 | 141 | runEntry := widget.NewEntry() 142 | runEntry.SetText(item.ItemRun) 143 | 144 | saveButton := widget.NewButton("Save", func() { 145 | item.ItemName = nameEntry.Text 146 | item.ItemDes = desEntry.Text 147 | item.ItemRun = runEntry.Text 148 | saveConfig() 149 | refreshUI(w) 150 | }) 151 | 152 | deleteButton := widget.NewButton("Delete Item", func() { 153 | dialog.ShowConfirm("Delete Item", "Are you sure you want to delete this item?", func(b bool) { 154 | if b { 155 | config.Lists[listID].Item = append(config.Lists[listID].Item[:itemID], config.Lists[listID].Item[itemID+1:]...) 156 | saveConfig() 157 | showListDetails(w, listID) 158 | refreshUI(w) 159 | } 160 | }, w) 161 | }) 162 | 163 | // Create a VBox to hold all sub-items 164 | subItemsContainer := container.NewVBox() 165 | 166 | // Add all sub-items to the container 167 | for i, subItem := range item.SubItems { 168 | subItemCard := widget.NewCard( 169 | subItem.ItemName, 170 | "", 171 | container.NewVBox( 172 | widget.NewLabel("Description: "+subItem.ItemDes), 173 | widget.NewLabel("Run Command: "+subItem.ItemRun), 174 | widget.NewButton("Delete Sub Item", func(index int) func() { 175 | return func() { 176 | item.SubItems = append(item.SubItems[:index], item.SubItems[index+1:]...) 177 | saveConfig() 178 | showItemDetails(w, listID, itemID) 179 | refreshUI(w) 180 | } 181 | }(i)), 182 | ), 183 | ) 184 | subItemsContainer.Add(subItemCard) 185 | } 186 | 187 | // Wrap the sub-items container in a scroll container 188 | subItemsScroll := container.NewScroll(subItemsContainer) 189 | subItemsScroll.SetMinSize(fyne.NewSize(300, 200)) 190 | 191 | addSubItemButton := widget.NewButton("Add Sub Item", func() { 192 | addNewSubItem(w, listID, itemID) 193 | }) 194 | 195 | content := container.NewVBox( 196 | nameEntry, 197 | desEntry, 198 | runEntry, 199 | saveButton, 200 | deleteButton, 201 | widget.NewLabel("Sub Items:"), 202 | subItemsScroll, 203 | addSubItemButton, 204 | ) 205 | 206 | updateDetailView(w, content) 207 | refreshUI(w) 208 | } 209 | 210 | func updateDetailView(w fyne.Window, content fyne.CanvasObject) { 211 | split := w.Content().(*container.Split) 212 | split.Trailing = content 213 | w.Content().Refresh() 214 | } 215 | 216 | func addNewList(w fyne.Window) { 217 | nameEntry := widget.NewEntry() 218 | nameEntry.SetPlaceHolder("Enter list name") 219 | 220 | dialog.ShowForm("Add New List", "Add", "Cancel", []*widget.FormItem{ 221 | widget.NewFormItem("Name", nameEntry), 222 | }, func(b bool) { 223 | if b { 224 | newList := List{Name: nameEntry.Text, Item: []Item{}} 225 | config.Lists = append(config.Lists, newList) 226 | saveConfig() 227 | refreshUI(w) 228 | } 229 | }, w) 230 | } 231 | 232 | func addNewItem(w fyne.Window, listID widget.ListItemID) { 233 | nameEntry := widget.NewEntry() 234 | nameEntry.SetPlaceHolder("Enter item name") 235 | 236 | desEntry := widget.NewMultiLineEntry() 237 | desEntry.SetPlaceHolder("Enter item description") 238 | desEntry.SetMinRowsVisible(3) 239 | 240 | runEntry := widget.NewEntry() 241 | runEntry.SetPlaceHolder("Enter item run command") 242 | 243 | form := &widget.Form{ 244 | Items: []*widget.FormItem{ 245 | widget.NewFormItem("Name", nameEntry), 246 | widget.NewFormItem("Description", desEntry), 247 | widget.NewFormItem("Run Command", runEntry), 248 | }, 249 | OnSubmit: func() { 250 | newItem := Item{ 251 | ItemName: nameEntry.Text, 252 | ItemDes: desEntry.Text, 253 | ItemRun: runEntry.Text, 254 | } 255 | config.Lists[listID].Item = append(config.Lists[listID].Item, newItem) 256 | saveConfig() 257 | showListDetails(w, listID) 258 | refreshUI(w) 259 | }, 260 | } 261 | 262 | content := container.NewVBox( 263 | widget.NewLabel("Add New Item"), 264 | form, 265 | ) 266 | 267 | dialog := dialog.NewCustom("Add New Item", "Cancel", content, w) 268 | dialog.SetOnClosed(func() { 269 | refreshUI(w) 270 | }) 271 | 272 | dialog.Resize(fyne.NewSize(400, 300)) 273 | 274 | dialog.Show() 275 | } 276 | 277 | func addNewSubItem(w fyne.Window, listID, itemID widget.ListItemID) { 278 | nameEntry := widget.NewEntry() 279 | nameEntry.SetPlaceHolder("Enter sub-item name") 280 | 281 | desEntry := widget.NewMultiLineEntry() 282 | desEntry.SetPlaceHolder("Enter sub-item description") 283 | desEntry.SetMinRowsVisible(3) 284 | 285 | runEntry := widget.NewEntry() 286 | runEntry.SetPlaceHolder("Enter sub-item run command") 287 | 288 | form := &widget.Form{ 289 | Items: []*widget.FormItem{ 290 | widget.NewFormItem("Name", nameEntry), 291 | widget.NewFormItem("Description", desEntry), 292 | widget.NewFormItem("Run Command", runEntry), 293 | }, 294 | OnSubmit: func() { 295 | newSubItem := SubItem{ 296 | ItemName: nameEntry.Text, 297 | ItemDes: desEntry.Text, 298 | ItemRun: runEntry.Text, 299 | } 300 | config.Lists[listID].Item[itemID].SubItems = append(config.Lists[listID].Item[itemID].SubItems, newSubItem) 301 | saveConfig() 302 | showItemDetails(w, listID, itemID) 303 | refreshUI(w) 304 | }, 305 | } 306 | 307 | content := container.NewVBox( 308 | widget.NewLabel("Add New Sub Item"), 309 | form, 310 | ) 311 | 312 | dialog := dialog.NewCustom("Add New Sub Item", "Cancel", content, w) 313 | dialog.SetOnClosed(func() { 314 | refreshUI(w) 315 | }) 316 | 317 | dialog.Resize(fyne.NewSize(400, 300)) 318 | 319 | dialog.Show() 320 | } 321 | 322 | func loadConfig() { 323 | file, err := ioutil.ReadFile("config.json") 324 | if err != nil { 325 | fmt.Println("Error reading file:", err) 326 | return 327 | } 328 | 329 | err = json.Unmarshal(file, &config) 330 | if err != nil { 331 | fmt.Println("Error unmarshalling JSON:", err) 332 | return 333 | } 334 | } 335 | 336 | func saveConfig() { 337 | file, err := json.MarshalIndent(config, "", " ") 338 | if err != nil { 339 | fmt.Println("Error marshalling JSON:", err) 340 | return 341 | } 342 | 343 | err = ioutil.WriteFile("config.json", file, 0644) 344 | if err != nil { 345 | fmt.Println("Error writing file:", err) 346 | return 347 | } 348 | } 349 | 350 | func refreshUI(w fyne.Window) { 351 | w.Content().Refresh() 352 | split := w.Content().(*container.Split) 353 | split.Leading.Refresh() 354 | split.Trailing.Refresh() 355 | } 356 | --------------------------------------------------------------------------------