├── 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 |
7 |
8 |
9 |
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 | 
28 |
29 | 第四步,下载依赖
30 | `go mod tidy`
31 | 
32 |
33 | 第五步,编译并执行
34 | `go build main.go`
35 | 
36 |
37 | 第六步,修改你的配置文件
38 |
39 | 1、使用编辑器直接编辑config.json
40 |
41 | 2、`go build up.go` 之后使用up工具去管理config.json
42 |
43 |
44 | ## 效果图1
45 |
46 | 
47 |
48 | ## 效果图2
49 |
50 | 
51 |
52 |
53 |
54 | 
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 |
--------------------------------------------------------------------------------