Комментирование, правки кода, исправление модальных окон
This commit is contained in:
parent
2e8eee135e
commit
300afb5d26
8 changed files with 299 additions and 618 deletions
436
log.txt
436
log.txt
|
|
@ -1,436 +0,0 @@
|
|||
time=13:34:29 29-12-2025 level=info msg=modal close msg get
|
||||
time=13:34:29 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=13:34:30 29-12-2025 level=info msg=sections user, focus 0, fields len 2
|
||||
time=13:34:30 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:30 29-12-2025 level=info msg=field choose_user, capture keys true
|
||||
time=13:34:30 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:30 29-12-2025 level=info msg=field choose_user, capture keys true
|
||||
time=13:34:31 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:31 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=13:34:32 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:34:32 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=13:34:32 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=13:34:32 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:32 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=13:34:32 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:34:32 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=13:34:33 29-12-2025 level=info msg=1, focus is 2
|
||||
time=13:34:33 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=13:34:33 29-12-2025 level=info msg=1, focus is 3
|
||||
time=13:34:33 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=13:34:33 29-12-2025 level=info msg=1, focus is 4
|
||||
time=13:34:33 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=13:34:34 29-12-2025 level=info msg=sections domain, focus 5, fields len 5
|
||||
time=13:34:34 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:34 29-12-2025 level=info msg=field database_type, capture keys false
|
||||
time=13:34:35 29-12-2025 level=info msg=sections database, focus 1, fields len 1
|
||||
time=13:34:36 29-12-2025 level=info msg=sections paths, focus 0, fields len 1
|
||||
time=13:34:37 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:37 29-12-2025 level=info msg=field debian_root, capture keys true
|
||||
time=13:34:37 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:34:37 29-12-2025 level=info msg=field debian_root, capture keys true
|
||||
time=13:34:37 29-12-2025 level=info msg=sections paths, focus 0, fields len 1
|
||||
time=13:54:35 29-12-2025 level=info msg=modal close msg get
|
||||
time=13:54:37 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=13:54:38 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:38 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=13:54:38 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:54:38 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=13:54:39 29-12-2025 level=info msg=2
|
||||
time=13:54:39 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:54:39 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=13:54:40 29-12-2025 level=info msg=sections user, focus 0, fields len 2
|
||||
time=13:54:40 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:40 29-12-2025 level=info msg=field choose_user, capture keys true
|
||||
time=13:54:41 29-12-2025 level=info msg=sections user, focus 0, fields len 2
|
||||
time=13:54:41 29-12-2025 level=info msg=chose {FieldIndex:1 Value:Existing}
|
||||
time=13:54:42 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:42 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=13:54:42 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:54:42 29-12-2025 level=info msg=field select_user, capture keys false
|
||||
time=13:54:42 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=13:54:42 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:42 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=13:54:43 29-12-2025 level=info msg=1, focus is 1
|
||||
time=13:54:43 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=13:54:43 29-12-2025 level=info msg=1, focus is 2
|
||||
time=13:54:43 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=13:54:43 29-12-2025 level=info msg=1, focus is 3
|
||||
time=13:54:43 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=13:54:43 29-12-2025 level=info msg=1, focus is 4
|
||||
time=13:54:43 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=13:54:44 29-12-2025 level=info msg=sections domain, focus 5, fields len 5
|
||||
time=13:54:44 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:44 29-12-2025 level=info msg=field database_type, capture keys false
|
||||
time=13:54:44 29-12-2025 level=info msg=sections database, focus 1, fields len 1
|
||||
time=13:54:45 29-12-2025 level=info msg=1, focus is 0
|
||||
time=13:54:45 29-12-2025 level=info msg=field debian_root, capture keys false
|
||||
time=13:54:45 29-12-2025 level=info msg=sections paths, focus 1, fields len 1
|
||||
time=13:57:35 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:00:04 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:08:24 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:08:25 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:08:25 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:08:25 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:08:26 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:08:26 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:08:26 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=14:08:28 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:08:28 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:28:24 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:28:25 29-12-2025 level=info msg=2
|
||||
time=14:28:25 29-12-2025 level=info msg=2
|
||||
time=14:28:25 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:28:26 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:28:26 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:28:26 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:26 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:28:27 29-12-2025 level=info msg=2
|
||||
time=14:28:27 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:27 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:28:28 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:28:28 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:28:29 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:29 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:28:29 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:28:29 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:28:29 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:29 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:28:49 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:28:50 29-12-2025 level=info msg=2
|
||||
time=14:28:50 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:28:50 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:28:50 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:28:51 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:51 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:28:52 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=14:28:52 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:28:52 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:28:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:28:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:28:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:28:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:53 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:29:53 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:29:53 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:29:53 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:29:53 29-12-2025 level=info msg=2
|
||||
time=14:29:53 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:53 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:54 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:54 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:29:54 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:29:54 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:29:54 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:29:54 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:30:06 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:30:08 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:31:23 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:31:23 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:31:23 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:31:23 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:31:24 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=14:32:32 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:38:03 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:38:04 29-12-2025 level=info msg=2
|
||||
time=14:38:04 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:38:05 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:38:05 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:38:05 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:38:05 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:38:06 29-12-2025 level=info msg=2
|
||||
time=14:38:06 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:38:06 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:38:07 29-12-2025 level=info msg=2
|
||||
time=14:38:07 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:38:07 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:38:08 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=14:38:08 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:38:08 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:38:09 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:38:09 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:38:09 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:38:09 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:38:09 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:38:09 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:38:10 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:38:10 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:39:11 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:39:11 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:39:52 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:40:10 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:40:12 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:40:31 29-12-2025 level=info msg=modal close msg get
|
||||
time=14:40:32 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=14:40:32 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:40:32 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=14:40:32 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:40:32 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=14:40:33 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=14:40:33 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:40:33 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:40:33 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:40:33 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:40:34 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:40:34 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:40:34 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:40:34 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:40:34 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:40:34 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:40:34 29-12-2025 level=info msg=2
|
||||
time=14:41:48 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:41:48 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=14:41:48 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:48 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=14:41:48 29-12-2025 level=info msg=1, focus is 2
|
||||
time=14:41:48 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=14:41:48 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:41:48 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:41:48 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:41:48 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:41:49 29-12-2025 level=info msg=sections domain, focus 3, fields len 5
|
||||
time=14:41:49 29-12-2025 level=info msg=1, focus is 3
|
||||
time=14:41:49 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=14:41:49 29-12-2025 level=info msg=1, focus is 4
|
||||
time=14:41:49 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=14:41:49 29-12-2025 level=info msg=sections domain, focus 5, fields len 5
|
||||
time=14:41:52 29-12-2025 level=info msg=1, focus is 0
|
||||
time=14:41:52 29-12-2025 level=info msg=field ssl_type, capture keys false
|
||||
time=14:41:53 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:53 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:41:53 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:53 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:41:54 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:54 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:41:54 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:55 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:56 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:57 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:58 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:58 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:41:58 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:58 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:41:59 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:41:59 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:41:59 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:00 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:00 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:00 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:00 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:00 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:01 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:01 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:01 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:01 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:01 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:02 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:02 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:02 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:42:03 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:03 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:03 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:03 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:03 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:04 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:04 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:04 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:04 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:05 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:42:05 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:05 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:05 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:05 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:06 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:06 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:06 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:06 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:06 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:07 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:07 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:07 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:07 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:07 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:08 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:08 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:09 29-12-2025 level=info msg=sections ssl, focus 1, fields len 3
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:10 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:10 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:11 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:11 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:12 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:12 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:12 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:12 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:12 29-12-2025 level=info msg=1, focus is 1
|
||||
time=14:42:12 29-12-2025 level=info msg=field ssl_cert_path, capture keys true
|
||||
time=14:42:16 29-12-2025 level=info msg=modal close msg get
|
||||
time=15:05:11 29-12-2025 level=info msg=modal close msg get
|
||||
time=15:05:12 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=15:17:37 29-12-2025 level=info msg=modal close msg get
|
||||
time=15:24:20 29-12-2025 level=info msg=modal close msg get
|
||||
time=16:26:13 29-12-2025 level=info msg=modal close msg get
|
||||
time=16:26:13 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=16:26:14 29-12-2025 level=info msg=1, focus is 0
|
||||
time=16:26:14 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=16:26:14 29-12-2025 level=info msg=1, focus is 1
|
||||
time=16:26:14 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=16:26:58 29-12-2025 level=info msg=modal close msg get
|
||||
time=16:49:25 29-12-2025 level=info msg=modal close msg get
|
||||
time=17:01:58 29-12-2025 level=info msg=modal close msg get
|
||||
time=17:04:53 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=17:04:54 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:04:54 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=17:04:54 29-12-2025 level=info msg=1, focus is 1
|
||||
time=17:04:54 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=17:04:54 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=17:04:55 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:04:55 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=17:04:55 29-12-2025 level=info msg=1, focus is 1
|
||||
time=17:04:55 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=17:04:55 29-12-2025 level=info msg=1, focus is 2
|
||||
time=17:04:55 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=17:04:55 29-12-2025 level=info msg=1, focus is 1
|
||||
time=17:04:55 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=17:04:55 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:04:55 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=17:04:56 29-12-2025 level=info msg=sections domain, focus 5, fields len 5
|
||||
time=17:04:56 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:04:56 29-12-2025 level=info msg=field database_type, capture keys false
|
||||
time=17:04:57 29-12-2025 level=info msg=sections database, focus 1, fields len 1
|
||||
time=17:04:57 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:04:57 29-12-2025 level=info msg=field debian_root, capture keys false
|
||||
time=17:04:57 29-12-2025 level=info msg=sections paths, focus 1, fields len 1
|
||||
time=17:43:36 29-12-2025 level=info msg=modal close msg get
|
||||
time=17:43:37 29-12-2025 level=info msg=sections Repko about, focus 0, fields len 0
|
||||
time=17:43:38 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:43:38 29-12-2025 level=info msg=field choose_user, capture keys false
|
||||
time=17:43:38 29-12-2025 level=info msg=1, focus is 1
|
||||
time=17:43:38 29-12-2025 level=info msg=field new_user, capture keys false
|
||||
time=17:43:39 29-12-2025 level=info msg=sections user, focus 2, fields len 2
|
||||
time=17:43:39 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:43:39 29-12-2025 level=info msg=field api_domain, capture keys false
|
||||
time=17:43:39 29-12-2025 level=info msg=1, focus is 1
|
||||
time=17:43:39 29-12-2025 level=info msg=field panel_domain, capture keys false
|
||||
time=17:43:39 29-12-2025 level=info msg=1, focus is 2
|
||||
time=17:43:39 29-12-2025 level=info msg=field cookie_domain, capture keys false
|
||||
time=17:43:40 29-12-2025 level=info msg=1, focus is 3
|
||||
time=17:43:40 29-12-2025 level=info msg=field is_secure, capture keys false
|
||||
time=17:43:40 29-12-2025 level=info msg=1, focus is 4
|
||||
time=17:43:40 29-12-2025 level=info msg=field server_address, capture keys false
|
||||
time=17:43:40 29-12-2025 level=info msg=sections domain, focus 5, fields len 5
|
||||
time=17:43:40 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:43:40 29-12-2025 level=info msg=field database_type, capture keys false
|
||||
time=17:43:41 29-12-2025 level=info msg=sections database, focus 1, fields len 1
|
||||
time=17:43:41 29-12-2025 level=info msg=1, focus is 0
|
||||
time=17:43:41 29-12-2025 level=info msg=field debian_root, capture keys false
|
||||
time=17:43:41 29-12-2025 level=info msg=sections paths, focus 1, fields len 1
|
||||
|
|
@ -155,6 +155,7 @@ func ComputeValue(computeExpr string, app *types.PackageApplication) (interface{
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// Recompute производит перерасчет всех полей
|
||||
func Recompute(app *types.PackageApplication) {
|
||||
for i := range app.Sections {
|
||||
section := &app.Sections[i]
|
||||
|
|
@ -162,7 +163,8 @@ func Recompute(app *types.PackageApplication) {
|
|||
field := §ion.Fields[j]
|
||||
if field.FieldType == "compute" && field.Compute != "" {
|
||||
if field.Variable != nil {
|
||||
continue //?почему мы пропускаем
|
||||
// todo почему мы пропускаем?
|
||||
continue
|
||||
}
|
||||
value, err := ComputeValue(field.Compute, app)
|
||||
if err != nil {
|
||||
|
|
@ -174,8 +176,10 @@ func Recompute(app *types.PackageApplication) {
|
|||
}
|
||||
}
|
||||
|
||||
// DefineExprCustomFunctions возвращает собственные функции expr
|
||||
func DefineExprCustomFunctions() []expr.Option {
|
||||
return []expr.Option{
|
||||
//вычисляет значение по заданному регулярному выражению
|
||||
expr.Function("regex",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
|
|
@ -191,6 +195,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
return re.ReplaceAllString(input, repl), nil
|
||||
}, new(func(string, string, string) string),
|
||||
),
|
||||
//проверяет совпадение переданного значения и указанного регулярного выражения
|
||||
expr.Function("regex_match",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
|
|
@ -207,6 +212,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
|
||||
}, new(func(string, string) bool),
|
||||
),
|
||||
//возвращает собственный текст ошибки
|
||||
expr.Function("error",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -216,6 +222,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
},
|
||||
new(func(string) error),
|
||||
),
|
||||
//возвращает тело ответа по указанному адресу
|
||||
expr.Function("http_get",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -232,6 +239,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
},
|
||||
new(func(string) string),
|
||||
),
|
||||
//возвращает результат выполнения команды из оболочки
|
||||
expr.Function("shell",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -250,6 +258,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
},
|
||||
new(func(string) error),
|
||||
),
|
||||
//возвращает значение указанной переменной из env
|
||||
expr.Function("env",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -258,6 +267,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
return os.Getenv(fmt.Sprintf("%v", args[0])), nil
|
||||
}, new(func(string) error),
|
||||
),
|
||||
//проверяет наличие файла в системе
|
||||
expr.Function("file_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -268,6 +278,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
return err == nil, nil
|
||||
}, new(func(string) bool),
|
||||
),
|
||||
//проверяет наличие директории в системе
|
||||
expr.Function("dir_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -284,6 +295,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
return false, nil
|
||||
}, new(func(string) bool),
|
||||
),
|
||||
//проверяет доступность порта
|
||||
expr.Function("port_free",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -294,6 +306,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
return cmd.Run() != nil, nil
|
||||
}, new(func(string) error),
|
||||
),
|
||||
//проверяет наличие пользователя в системе
|
||||
expr.Function("user_exists",
|
||||
func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) == 0 {
|
||||
|
|
@ -307,6 +320,7 @@ func DefineExprCustomFunctions() []expr.Option {
|
|||
}
|
||||
}
|
||||
|
||||
// SpecialValueHandle получаем необычные значения полей
|
||||
func SpecialValueHandle(special string) interface{} {
|
||||
specialValue := strings.Split(special, "code:")[1]
|
||||
switch specialValue {
|
||||
|
|
|
|||
|
|
@ -10,21 +10,21 @@ import (
|
|||
"github.com/charmbracelet/lipgloss/table"
|
||||
)
|
||||
|
||||
// FinalModel структур описывающая модель отрисовки таблицы переменных
|
||||
type FinalModel struct {
|
||||
variables []types.Variable
|
||||
table *table.Table
|
||||
viewport viewport.Model
|
||||
ready bool
|
||||
width int
|
||||
height int
|
||||
variables []types.Variable //Список переменных
|
||||
table *table.Table //Таблица для вывода
|
||||
viewport viewport.Model //Объект куда помещена таблица для возможности прокручивать её
|
||||
width int //Ширина окна
|
||||
height int //Высота окна
|
||||
}
|
||||
|
||||
// NewFinalModel создается новую модель таблицы переменных
|
||||
func NewFinalModel(variables []types.Variable, width, height int) *FinalModel {
|
||||
m := &FinalModel{
|
||||
variables: variables,
|
||||
width: width,
|
||||
height: height,
|
||||
ready: false,
|
||||
}
|
||||
if width > 0 && height > 0 {
|
||||
m.initViewport(width, height)
|
||||
|
|
@ -36,60 +36,70 @@ func (m *FinalModel) Init() tea.Cmd {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Update отслеживает действия со таблицей
|
||||
func (m *FinalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
//Отслеживаем нажатие клавиш
|
||||
switch msg.String() {
|
||||
case "up":
|
||||
//Обработчик up для прокручивания вверх на 1 позицию
|
||||
m.viewport.ScrollUp(1)
|
||||
case "down":
|
||||
//Обработчик down для прокручивания вниз на 1 позицию
|
||||
m.viewport.ScrollDown(1)
|
||||
case "left":
|
||||
//Обработчик left для прокручивания влево на 1 позицию
|
||||
m.viewport.ScrollLeft(1)
|
||||
case "right":
|
||||
//Обработчик right для прокручивания вправо на 1 позицию
|
||||
m.viewport.ScrollRight(1)
|
||||
case "pageup":
|
||||
//Обработчик pageup для прокручивания вверх на половину допустимой высоты viewport
|
||||
m.viewport.HalfPageUp()
|
||||
case "pagedown":
|
||||
//Обработчик pagedown для прокручивания вниз на половину допустимой высоты viewport
|
||||
m.viewport.HalfPageDown()
|
||||
case "home":
|
||||
//Обработчик home для перехода в самый вверх
|
||||
m.viewport.GotoTop()
|
||||
case "end":
|
||||
//Обработчик end для перехода в самый низ
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
//Отслеживаем и записываем новые размер окна, вносим изменения в размеры viewport
|
||||
m.height = msg.Height
|
||||
m.width = msg.Width
|
||||
m.initViewport(msg.Width, msg.Height)
|
||||
}
|
||||
//Если сообщение было иным, передаем его напрямую в viewport
|
||||
//todo проверить есть ли эти функции уже у viewport.Update, возможно мой код дублирует
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// View формирует строку для вывода таблицы переменных
|
||||
func (m *FinalModel) View() string {
|
||||
if !m.ready {
|
||||
return "Загрузка таблицы"
|
||||
}
|
||||
//Получаем размеры контейнера и ширину контента
|
||||
containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height)
|
||||
|
||||
m.rebuildTable(contentWidth)
|
||||
|
||||
//Формируем шапку с заголовком
|
||||
header := ui.FinalHeader.
|
||||
Width(contentWidth).
|
||||
Render("Итоговые значения переменных")
|
||||
//Формируем "подвал"
|
||||
footer := ui.FinalFooter.
|
||||
Width(contentWidth).
|
||||
Render(fmt.Sprintf("Строк: %d • q — выход • ↑↓ / jk — скролл", len(m.variables)))
|
||||
Render(fmt.Sprintf("Количество переменных: %d", len(m.variables)))
|
||||
|
||||
renderedTable := m.table.Render()
|
||||
content := lipgloss.NewStyle().
|
||||
Align(lipgloss.Center).
|
||||
Render(renderedTable)
|
||||
m.viewport.SetContent(content)
|
||||
body := m.viewport.View()
|
||||
fullContent := lipgloss.JoinVertical(lipgloss.Top, header, body, footer)
|
||||
//Обновляем отображение таблицы
|
||||
m.rebuildTable(contentWidth)
|
||||
//Формируем единый контент из шапки, видимой области и подвала
|
||||
fullContent := lipgloss.JoinVertical(lipgloss.Top, header, m.viewport.View(), footer)
|
||||
|
||||
//Формируем главный контейнер
|
||||
container := ui.AppContainer.
|
||||
Width(containerWidth).
|
||||
Height(containerHeight).
|
||||
|
|
@ -103,6 +113,7 @@ func (m *FinalModel) View() string {
|
|||
)
|
||||
}
|
||||
|
||||
// initViewport создание видимой области таблицы
|
||||
func (m *FinalModel) initViewport(width, height int) {
|
||||
|
||||
_, _, contentWidth, contentHeight := ui.ContentSizes(width, height)
|
||||
|
|
@ -110,15 +121,15 @@ func (m *FinalModel) initViewport(width, height int) {
|
|||
tableWidth := contentWidth - 2*1
|
||||
tableHeight := int(float64(contentHeight)*ui.TableWithRatio) - 2*1
|
||||
|
||||
//Создание видимой области, куда будет помещена таблица
|
||||
m.viewport = viewport.New(contentWidth, tableHeight)
|
||||
//размеры для viewport в нем будет находиться таблица и перемещаться в зависимости от команд
|
||||
|
||||
m.ready = true
|
||||
//Обновление таблицы
|
||||
m.rebuildTable(tableWidth)
|
||||
m.viewport.SetContent(m.table.Render())
|
||||
}
|
||||
|
||||
func (m *FinalModel) rebuildTable(width int) {
|
||||
//Если переменных нет, то делаем спец таблицу
|
||||
if len(m.variables) == 0 {
|
||||
m.table = table.New().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
|
|
@ -126,9 +137,11 @@ func (m *FinalModel) rebuildTable(width int) {
|
|||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
return ui.FinalTableHeader
|
||||
}).Headers("Нет переменных")
|
||||
m.viewport.SetContent(m.table.Render())
|
||||
return
|
||||
}
|
||||
variableNameColWidth := 15
|
||||
//Вычисляем размеры колонок
|
||||
variableNameColWidth := 20
|
||||
defaultValueColWidth := 25
|
||||
usedValueColWidth := width - variableNameColWidth - defaultValueColWidth - 4*1
|
||||
//ширина таблицы - ширина первой колонки - ширина второй колонки - 4 границы (2 крайние, 2 между колонками)
|
||||
|
|
@ -136,6 +149,7 @@ func (m *FinalModel) rebuildTable(width int) {
|
|||
usedValueColWidth = 30
|
||||
}
|
||||
|
||||
//Создаем модель таблицы
|
||||
t := table.New().
|
||||
Width(variableNameColWidth+defaultValueColWidth+usedValueColWidth+4).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
|
|
@ -162,6 +176,7 @@ func (m *FinalModel) rebuildTable(width int) {
|
|||
}).
|
||||
Headers("Variable name", "Default value", "Used value")
|
||||
|
||||
//Заполняем таблицу данными
|
||||
for _, v := range m.variables {
|
||||
used := fmt.Sprintf("%v", v.Value)
|
||||
if used == "" {
|
||||
|
|
@ -169,5 +184,9 @@ func (m *FinalModel) rebuildTable(width int) {
|
|||
}
|
||||
t.Row(v.Name, v.Default, used)
|
||||
}
|
||||
//записываем таблицу в модель
|
||||
m.table = t
|
||||
|
||||
//Помещение в видимую область таблицы
|
||||
m.viewport.SetContent(m.table.Render())
|
||||
}
|
||||
|
|
|
|||
101
setup/form.go
101
setup/form.go
|
|
@ -10,20 +10,23 @@ import (
|
|||
"github.com/muesli/reflow/wordwrap"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FormModel структура описывающая модель отрисовки формы настройки
|
||||
type FormModel struct {
|
||||
CurrentSection *types.Section
|
||||
FieldModels []fields.FieldModel
|
||||
focus int
|
||||
renderedContent string
|
||||
width int
|
||||
height int
|
||||
CurrentSection *types.Section //Указатель секции с которой надо работать
|
||||
FieldModels []fields.FieldModel //Массив моделей полей
|
||||
focus int //Фокус для отслеживания с каким объектом надо работать
|
||||
renderedContent string //Строка хранения информации о приложении
|
||||
width int //Ширина окна
|
||||
height int //Высота окна
|
||||
}
|
||||
|
||||
// NewFormModel создает новую модель формы на основе данных настраиваемого приложения
|
||||
func NewFormModel(application *types.PackageApplication, sectionIdx, focus, width, height int) *FormModel {
|
||||
//Получаем секцию которую надо отрисовать
|
||||
currentSection := &application.Sections[sectionIdx]
|
||||
//Заполняем массив моделей полей с учетом условий
|
||||
var fieldModels []fields.FieldModel
|
||||
for _, sectionField := range currentSection.Fields {
|
||||
if !CheckCondition(sectionField.Condition, application) {
|
||||
|
|
@ -66,6 +69,7 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus, widt
|
|||
fieldModels = append(fieldModels, fields.NewInputModel(sectionField.Name, fmt.Sprintf("%v", sectionField.Variable.Value)))
|
||||
}
|
||||
}
|
||||
//Устанавливаем фокус (фокус может быть не равен нулю, если новая форма сформирована после Sugar события, который обновляет форму в ТЕКУЩЕМ состоянии)
|
||||
if len(fieldModels) > focus {
|
||||
fieldModels[focus].Focus()
|
||||
}
|
||||
|
|
@ -76,6 +80,7 @@ func NewFormModel(application *types.PackageApplication, sectionIdx, focus, widt
|
|||
width: width,
|
||||
height: height,
|
||||
}
|
||||
//Формируем описание приложения если секция установлена About
|
||||
if currentSection.Type == types.SectionAbout {
|
||||
m.renderedContent = simpleAboutRender(currentSection.Paragraphs, width)
|
||||
m.focus = 0
|
||||
|
|
@ -87,10 +92,12 @@ func (m *FormModel) Init() tea.Cmd {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Update отслеживает действия с формой
|
||||
func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
//Отслеживаем и записываем новые размеры окна, обновляем рендер about
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
if m.CurrentSection.Type == types.SectionAbout {
|
||||
|
|
@ -98,13 +105,22 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
//Отслеживаем нажатие клавиш формы
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
//Обработчик клавиши escape на форме нужен для закрытия полей поддерживающих открытие.
|
||||
//Обходим все доступные в текущей форме поля и закрываем те, что были открыты.
|
||||
//Проверять на фокус не нужно, так как не предполагается, что у нас может быть открыто сразу несколько полей.
|
||||
//Поэтому просто возвращаем команду по возврату сообщение об обновлении полей.
|
||||
for _, field := range m.FieldModels {
|
||||
//проверяем открыто ли поле
|
||||
if field.CaptureKeys() {
|
||||
//обновляем поля если оно открыто
|
||||
return m, m.updateFields(msg)
|
||||
}
|
||||
}
|
||||
//Если открытых полей не было найдено, значит escape была нажата для закрытия настройки,
|
||||
//возвращаем команду по возврату сообщения об открытии модального окна
|
||||
return m, func() tea.Msg {
|
||||
return types.ShowModalMsg{
|
||||
Type: types.ModalWarning,
|
||||
|
|
@ -123,23 +139,28 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
case "enter":
|
||||
logrus.Infof("sections %s, focus %d, fields len %d", m.CurrentSection.Name, m.focus, len(m.FieldModels))
|
||||
//Обработчик клавиши enter на форме нужен для перехода на следующую секцию.
|
||||
//Проверяем где сейчас находится фокус, если его позиция равняется количеству полей,
|
||||
//значит он вышел за пределы формы и находится на кнопке next section, что ставится последней.
|
||||
//После этого мы сбрасываем фокус и возвращаем команду по возврату сообщения о переходе на следующую секцию
|
||||
if len(m.FieldModels) == m.focus {
|
||||
m.focus = 0
|
||||
return m, func() tea.Msg {
|
||||
return types.NextSectionMsg{}
|
||||
}
|
||||
}
|
||||
case "tab", "down", "shift+tab", "up":
|
||||
//Итак, тут надо учесть динамические поля.
|
||||
//
|
||||
case "tab", "shift+tab", "down", "up":
|
||||
//Обработчик клавиш tab, shift+tab, down, up на форме нужен для перехода между объектами формы.
|
||||
//Получаем точное имя клавиши нажатия, после проверяем где сейчас находится фокус.
|
||||
//Если фокус не равен количеству полей, то значит фокус сейчас у поля формы.
|
||||
//Получаем имя поля (так как это старый фокус, то и поле на котором мы находимся это предыдущее поле, отсюда и название переменной),
|
||||
//Если поле не открыто, то мы можем перейти на следующее поле, увеличив или уменьшив фокус.
|
||||
//Если фокус равен количеству полей, значит он находится на кнопке переключения секций. Увеличиваем или уменьшаем фокус.
|
||||
key := msg.String()
|
||||
if m.focus != len(m.FieldModels) {
|
||||
logrus.Infof("1, focus is %d", m.focus)
|
||||
//todo исправить фокус
|
||||
field := m.FieldModels[m.focus]
|
||||
logrus.Infof("field %s, capture keys %t", field.GetLabel(), field.CaptureKeys())
|
||||
if !field.CaptureKeys() {
|
||||
prevField := m.FieldModels[m.focus]
|
||||
if !prevField.CaptureKeys() {
|
||||
if key == "tab" || key == "down" {
|
||||
m.focus++
|
||||
} else if key == "shift+tab" || key == "up" {
|
||||
|
|
@ -147,13 +168,17 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("2")
|
||||
if key == "tab" || key == "down" {
|
||||
m.focus++
|
||||
} else if key == "shift+tab" || key == "up" {
|
||||
m.focus--
|
||||
}
|
||||
}
|
||||
//Проверка правильности фокуса, чтобы не выйти за границы массива полей формы, необходимо проверить новое значение фокуса.
|
||||
//Если фокус стал больше чем количество полей формы, значит он вышел за пределы всей формы и находится уже даже ниже кнопки,
|
||||
//переводим его на первую позицию, то есть на 0.
|
||||
//Если фокус стал меньше 0, значит он вышел за пределы формы и находится выше первого поля формы,
|
||||
//переводим его на последнюю позицию, то есть на кнопку переключения секции, равную положение которой = количество полей на форме.
|
||||
if m.focus == len(m.FieldModels)+1 {
|
||||
m.focus = 0
|
||||
} else if m.focus < 0 {
|
||||
|
|
@ -161,30 +186,39 @@ func (m *FormModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
case types.SelectedOptionMsg:
|
||||
//Отслеживаем выбор опции в поле типа select
|
||||
//todo тут вообще логика нужна? Мы ведь в select, через m.chosen записываем значение, а после GetValue метод возвращаем m.chosen
|
||||
logrus.Infof("chose %+v", msg)
|
||||
}
|
||||
//Если не было возвращено сообщений, значит действие было произведено с полем, а не с самой формой.
|
||||
//Возвращаем результат функции обновления полей.
|
||||
return m, m.updateFields(msg)
|
||||
}
|
||||
|
||||
// View формирует строку для вывода формы и её контента.
|
||||
func (m *FormModel) View() string {
|
||||
|
||||
//Данные о размерах контейнера и ширины контента внутри
|
||||
containerWidth, containerHeight, contentWidth, _ := ui.ContentSizes(m.width, m.height)
|
||||
|
||||
formWidth := contentWidth - 2*1
|
||||
var strokes []string
|
||||
var strokes []string //отображаемые строки
|
||||
for i, field := range m.FieldModels {
|
||||
//Строка с учетом переноса поля.
|
||||
//Имеется ввиду, что если вводимое значение слишком длинное, то оно будет делиться на несколько строк.
|
||||
fieldStroke := wordwrap.String(field.View(), formWidth)
|
||||
//Если фокус равен текущему полю, то выделяем его.
|
||||
if i == m.focus {
|
||||
strokes = append(strokes, ui.FormFieldFocused.Render(fieldStroke))
|
||||
} else {
|
||||
strokes = append(strokes, ui.FormFieldUnfocused.Render(fieldStroke))
|
||||
}
|
||||
}
|
||||
|
||||
//Если текущая секция это секция About, то выводим её информацию.
|
||||
//Не предполагается, что информация о приложении и поля для настройки будут в одной секции, поэтому тут идет обнуление прошлых строк.
|
||||
if m.CurrentSection.Type == types.SectionAbout {
|
||||
strokes = append([]string{}, m.renderedContent)
|
||||
}
|
||||
|
||||
//Строка кнопки переключения секций.
|
||||
//Если позиция фокуса равняется количеству полей, значит он сейчас находится на кнопке.
|
||||
nextBtn := "[ next section ]"
|
||||
if m.focus == len(m.FieldModels) {
|
||||
strokes = append(strokes, ui.FormNextButton.Render(nextBtn))
|
||||
|
|
@ -192,6 +226,8 @@ func (m *FormModel) View() string {
|
|||
strokes = append(strokes, ui.FormFieldUnfocused.Render(nextBtn))
|
||||
}
|
||||
|
||||
//Основной контейнер приложения.
|
||||
//Устанавливаем ему ширину и высоту, объединяем все строки в одну с соединением, через двойной перевод строки.
|
||||
container := ui.AppContainer.
|
||||
Width(containerWidth).
|
||||
Height(containerHeight).
|
||||
|
|
@ -204,20 +240,28 @@ func (m *FormModel) View() string {
|
|||
)
|
||||
}
|
||||
|
||||
// updateFields обновляет поля и их внутренние данные, формирует единую команду действий для всех полей, в том числе возврат SugarMsg
|
||||
func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd {
|
||||
//Массив команд для выполнения
|
||||
var cmds []tea.Cmd
|
||||
for i, fieldModel := range m.FieldModels {
|
||||
var fieldCmd tea.Cmd
|
||||
var updated fields.FieldModel
|
||||
if i == m.focus { //если фокус установлен на текущем поле, то текущее поле активируем и передаем ему сообщения для обновления
|
||||
//Если фокус установлен на текущем поле, то нужно активировать его и передать ему сообщение для обновления.
|
||||
//Если фокус не установлен на текущем поле, то снимаем с него фокус и передаем ему сообщение не связанное с нажатием кнопок клавиатуры
|
||||
if i == m.focus {
|
||||
fieldModel.Focus()
|
||||
updated, fieldCmd = fieldModel.Update(msg)
|
||||
} else { //если фокус не установлен на текущем поле, то убираем с неё фокус, передаем сообщения, но только те, что не содержат действий клавиатуры
|
||||
} else {
|
||||
fieldModel.Blur()
|
||||
if _, ok := msg.(tea.KeyMsg); !ok {
|
||||
updated, fieldCmd = fieldModel.Update(msg)
|
||||
}
|
||||
}
|
||||
//Если поле успешно вернуло обновленное поле, то необходимо:
|
||||
//обновить модель поля в текущей форме;
|
||||
//проверить изменилось ли значение переменной в настройках приложения,
|
||||
//если изменилось, в массив команд помещаем команду по возврату сообщения Sugar
|
||||
if updated != nil {
|
||||
m.FieldModels[i] = updated
|
||||
for _, sectionField := range m.CurrentSection.Fields {
|
||||
|
|
@ -234,17 +278,21 @@ func (m *FormModel) updateFields(msg tea.Msg) tea.Cmd {
|
|||
}
|
||||
})
|
||||
sectionField.Variable.Value = newValue //recompute вызывать.
|
||||
//todo что? надо на гите посмотреть в истории, может я это уже сделал, ведь sugar Msg отправляется
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//Если есть команда поля, то также помещаем её в массив команд
|
||||
if fieldCmd != nil {
|
||||
cmds = append(cmds, fieldCmd)
|
||||
}
|
||||
}
|
||||
//Объединяем весь массив команду в одну
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// simpleAboutRender формирует единую строку, описывающую настраиваемое приложение.
|
||||
func simpleAboutRender(paragraphs []string, width int) string {
|
||||
var sb strings.Builder
|
||||
for _, p := range paragraphs {
|
||||
|
|
@ -256,9 +304,10 @@ func simpleAboutRender(paragraphs []string, width int) string {
|
|||
switch {
|
||||
case strings.HasPrefix(p, "# "):
|
||||
// Заголовок 1 уровня
|
||||
h1Len := utf8.RuneCountInString(p[2:])
|
||||
h1Offset := strings.Repeat(" ", (width-h1Len)/2)
|
||||
sb.WriteString(ui.AboutTitle.Render(h1Offset+p[2:]) + "\n\n")
|
||||
//h1Len := utf8.RuneCountInString(p[2:])
|
||||
//h1Offset := strings.Repeat(" ", (width-h1Len)/2)
|
||||
//sb.WriteString(ui.AboutTitle.Render(h1Offset+p[2:]) + "\n\n")
|
||||
sb.WriteString(ui.AboutTitle.Render(p[2:]) + "\n\n")
|
||||
case strings.HasPrefix(p, "## "):
|
||||
// Заголовок 2 уровня
|
||||
sb.WriteString(ui.AboutH2.Render(p[3:]) + "\n\n")
|
||||
|
|
|
|||
|
|
@ -10,38 +10,46 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ListApplicationItem структура для описывающая один элемент списка
|
||||
type ListApplicationItem struct {
|
||||
Application types.PackageApplication
|
||||
Application types.PackageApplication // Приложения для настройки
|
||||
}
|
||||
|
||||
// Title метод для получения имени пункта списка
|
||||
func (i ListApplicationItem) Title() string {
|
||||
return fmt.Sprintf("%s, v%s", i.Application.Name, i.Application.Version)
|
||||
}
|
||||
|
||||
// Description метод для получения описания пункта списка
|
||||
func (i ListApplicationItem) Description() string {
|
||||
return fmt.Sprintf("%s", i.Application.Description)
|
||||
}
|
||||
|
||||
// FilterValue метод для получения строки, по которой будет происходить фильтрация
|
||||
func (i ListApplicationItem) FilterValue() string {
|
||||
return i.Application.Name
|
||||
}
|
||||
|
||||
// ListApplicationsModel структура описывающая модель отрисовки списка приложений
|
||||
type ListApplicationsModel struct {
|
||||
List list.Model
|
||||
width int
|
||||
height int
|
||||
List list.Model //Готовый компонент модели списка
|
||||
width int //Ширина окна
|
||||
height int //Высота окна
|
||||
}
|
||||
|
||||
// NewListApplicationsModel создает новую модель списка на основе переданного массива приложений
|
||||
func NewListApplicationsModel(applications []types.PackageApplication, width, height int) *ListApplicationsModel {
|
||||
|
||||
//Заполняем массив приложений
|
||||
items := make([]list.Item, len(applications))
|
||||
for i, app := range applications {
|
||||
items[i] = ListApplicationItem{app}
|
||||
}
|
||||
|
||||
//delegate нужен для функционала работы с элементом списка
|
||||
delegate := list.NewDefaultDelegate()
|
||||
delegate.Styles.SelectedTitle = ui.ListSelectedStyle
|
||||
delegate.Styles.SelectedDesc = ui.ListSelectedStyle
|
||||
delegate.ShowDescription = false
|
||||
//Формируем новый список
|
||||
l := list.New(items, delegate, width, height)
|
||||
l.Title = "Приложения"
|
||||
l.Styles.Title = ui.ListTitleStyle
|
||||
|
|
@ -57,16 +65,22 @@ func (m *ListApplicationsModel) Init() tea.Cmd {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Update отслеживает действия со списком приложений
|
||||
func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
//Отслеживаем и записываем новые размеры окна
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
//Отслеживаем нажатия клавиш списка
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
//Обработчик enter нужен для подтверждения элемента списка.
|
||||
//Получаем информацию о выбранном приложении,
|
||||
//возвращаем команду по возврату сообщения о показе модального окна подтверждения настройки приложения.
|
||||
selected := m.List.SelectedItem().(ListApplicationItem)
|
||||
return m, func() tea.Msg {
|
||||
return types.ShowModalMsg{
|
||||
|
|
@ -89,23 +103,27 @@ func (m *ListApplicationsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
}
|
||||
//Если ранее не было возвращено команд, то передаем сообщение напрямую в список
|
||||
m.List, cmd = m.List.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// View формирует строку для вывода списка приложений
|
||||
func (m *ListApplicationsModel) View() string {
|
||||
//Строка отступа между списком и описанием
|
||||
gapStroke := " "
|
||||
//Данные о размерах контейнера и контента внутри
|
||||
containerWidth, containerHeight, contentWidth, contentHeight := ui.ContentSizes(m.width, m.height)
|
||||
|
||||
//Ширина списка = пропорция от ширины контента - границы для контейнера списка
|
||||
listWidth := int(float64(contentWidth)*ui.ListWithRatio) - 2*1
|
||||
//ширина листа = пропорция от ширины контента - границы для контейнера листа
|
||||
//Высота списка = высота контента - границы для контейнера списка
|
||||
listHeight := contentHeight - 2*1
|
||||
//высота листа = высота контента - границы для контейнера листа
|
||||
//Ширина описания = обратная пропорция списка от ширины контента - границы для контейнера описания - отступ от контейнера списка
|
||||
descWidth := int(float64(contentWidth)*(1-ui.ListWithRatio)) - 2*1 - len(gapStroke)
|
||||
//ширина описания = обратная пропорция листа от ширины контента - границы для контейнера описания - отступ от контейнера листа
|
||||
//Высота описания = высота контента - границы для контейнера описания
|
||||
descHeight := contentHeight - 2*1
|
||||
//высота листа = высота контента - границы для контейнера описания
|
||||
|
||||
//Установка размеров списка
|
||||
m.List.SetSize(listWidth, listHeight)
|
||||
|
||||
//logs.go logrus.DebugLevel set
|
||||
|
|
@ -118,30 +136,26 @@ func (m *ListApplicationsModel) View() string {
|
|||
descWidth, descHeight,
|
||||
)
|
||||
|
||||
listView := lipgloss.NewStyle().
|
||||
//Формируем строку списка
|
||||
listView := ui.ListContainerStyle.
|
||||
Width(listWidth).
|
||||
Height(listHeight).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(ui.ColorPurple).
|
||||
AlignVertical(lipgloss.Top).
|
||||
Render(m.List.View())
|
||||
|
||||
//Получаем описание
|
||||
var desc string
|
||||
if sel := m.List.SelectedItem(); sel != nil {
|
||||
desc = sel.(ListApplicationItem).Description()
|
||||
}
|
||||
|
||||
descView := ui.ListDescriptionStyle.
|
||||
//Формируем строку с описанием
|
||||
descView := ui.ListDescriptionContainerStyle.
|
||||
Width(descWidth).
|
||||
Height(descHeight).
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(ui.ColorError).
|
||||
AlignVertical(lipgloss.Top).
|
||||
Render(
|
||||
desc,
|
||||
)
|
||||
Render(desc)
|
||||
|
||||
//Объединяем список и описание в один контент с отступом
|
||||
body := lipgloss.JoinHorizontal(lipgloss.Center, listView, gapStroke, descView)
|
||||
//Формируем главный контейнер
|
||||
container := ui.AppContainer.
|
||||
Width(containerWidth).
|
||||
Height(containerHeight).
|
||||
|
|
|
|||
104
setup/main.go
104
setup/main.go
|
|
@ -3,9 +3,7 @@ package setup
|
|||
import (
|
||||
"bubble/bootstrap"
|
||||
"bubble/types"
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Start() {
|
||||
|
|
@ -15,23 +13,27 @@ func Start() {
|
|||
}
|
||||
}
|
||||
|
||||
// Main структура описывающая объект настроек
|
||||
type Main struct {
|
||||
current tea.Model
|
||||
overlay tea.Model
|
||||
Applications []types.PackageApplication
|
||||
CurrentApplication *types.PackageApplication
|
||||
CurrentSection *types.Section
|
||||
CurrentSectionIdx int
|
||||
Width int
|
||||
Height int
|
||||
current tea.Model //Текущая модель для отображения
|
||||
overlay tea.Model //Модель для отображения модальных окон
|
||||
Applications []types.PackageApplication //Список доступных приложений для настройки
|
||||
CurrentApplication *types.PackageApplication //Выбранное приложение для настройки
|
||||
CurrentSection *types.Section //Текущая секция
|
||||
CurrentSectionIdx int //Индекс текущей секции
|
||||
Width int //Ширина окна
|
||||
Height int //Высота окна
|
||||
}
|
||||
|
||||
// NewMain создаем новый объект настройки
|
||||
func NewMain() *Main {
|
||||
//Читаем все конфиги приложении
|
||||
apps := bootstrap.LoadApplications()
|
||||
m := &Main{
|
||||
Applications: apps,
|
||||
CurrentSectionIdx: 0,
|
||||
}
|
||||
//Записываем в текущую модель список приложений
|
||||
m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height)
|
||||
return m
|
||||
}
|
||||
|
|
@ -40,16 +42,20 @@ func (m *Main) Init() tea.Cmd {
|
|||
return m.current.Init()
|
||||
}
|
||||
|
||||
// Update отслеживает действия в объекте настроек
|
||||
func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
var cmd tea.Cmd
|
||||
//Если у нас установлен модель модального окна, то передаем сообщение в его функцию обновления,
|
||||
//при возврате команды модального окна записываем её в массив команд.
|
||||
//В противном случае передаем сообщение в функцию обновления текущей модели,
|
||||
//при возврате команды также записываем её в массив команд.
|
||||
if m.overlay != nil {
|
||||
var cmd tea.Cmd
|
||||
m.overlay, cmd = m.overlay.Update(msg)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
} else {
|
||||
var cmd tea.Cmd
|
||||
m.current, cmd = m.current.Update(msg)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
|
|
@ -57,17 +63,17 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
//Отслеживаем и записываем новые размеры окна.
|
||||
//Передавать сообщение в модель текущую или модального окна для вызова их обработки изменения размеров не нужно,
|
||||
//они получили WindowSizeMsg выше, когда мы передаем им переменную msg.
|
||||
m.Width = msg.Width
|
||||
m.Height = msg.Height
|
||||
if m.current != nil {
|
||||
m.current, _ = m.current.Update(msg)
|
||||
}
|
||||
if m.overlay != nil {
|
||||
m.overlay, _ = m.overlay.Update(msg)
|
||||
}
|
||||
case tea.KeyMsg:
|
||||
//Отслеживаем нажатие клавиш приложения
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
//Обработчик клавиш ctrl+c у приложения нужен для его закрытия.
|
||||
//Возвращаем команду по возврату сообщения о показе модального окна
|
||||
return m, func() tea.Msg {
|
||||
return types.ShowModalMsg{
|
||||
Type: types.ModalWarning,
|
||||
|
|
@ -87,8 +93,12 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
}
|
||||
case types.NextSectionMsg:
|
||||
//Получаем сообщение о переходе на следующую секцию настраиваемого приложения.
|
||||
//Увеличиваем индекс текущей секции.
|
||||
m.CurrentSectionIdx++
|
||||
//Если новый индекс равен
|
||||
if m.CurrentSectionIdx == len(m.CurrentApplication.Sections) {
|
||||
//todo что делать когда секций больше нужного?
|
||||
return m, tea.Quit
|
||||
}
|
||||
for sectionIdx := m.CurrentSectionIdx; sectionIdx < len(m.CurrentApplication.Sections); sectionIdx++ {
|
||||
|
|
@ -134,28 +144,51 @@ func (m *Main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
break
|
||||
}
|
||||
}
|
||||
//если секция не была найдена, то выходим
|
||||
//Если секция доступная к выводу не была найдена, то возвращаем модальное окно с выходом к списку приложений
|
||||
if m.CurrentSection == nil {
|
||||
logrus.Infof("select app, eq nil, out")
|
||||
return m, tea.Quit
|
||||
return m, func() tea.Msg {
|
||||
return types.ShowModalMsg{
|
||||
Type: types.ModalError,
|
||||
Title: "Ошибка",
|
||||
Message: "Ошибка выбора приложения. Указанное приложение не имеет доступных секций." +
|
||||
"Проверьте конфиг настраиваемого приложения на предмет выполнения условий секций",
|
||||
ModalButtons: []types.ModalButton{{Text: "Вернуться к списку приложений", Value: true}},
|
||||
OnResult: func(result any) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return types.BackToListMsg{}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
//создаем новую форму
|
||||
m.current = NewFormModel(m.CurrentApplication, m.CurrentSectionIdx, 0, m.Width, m.Height)
|
||||
case types.BackToListMsg:
|
||||
//Получаем сообщение о возврате к списку приложений.
|
||||
//Стираем текущие приложение, стираем текущую секцию, стираем индекс секции, записываем в текущую модель список приложений
|
||||
m.CurrentApplication = nil
|
||||
m.CurrentSection = nil
|
||||
m.CurrentSectionIdx = 0
|
||||
m.current = NewListApplicationsModel(m.Applications, m.Width, m.Height)
|
||||
case types.ShowModalMsg:
|
||||
//Получаем сообщение о показе модального окна.
|
||||
//В поле модального окна записываем новый объект модального окна на основе сообщений.
|
||||
//НЕ СТИРАЕМ ТЕКУЩЕЕ ПРИЛОЖЕНИЕ
|
||||
m.overlay = NewModalModel(msg, m.Width, m.Height)
|
||||
return m, nil
|
||||
case types.CloseModalMsg:
|
||||
//Получаем сообщение о закрытии модального окна.
|
||||
//Стираем модальное окно.
|
||||
m.overlay = nil
|
||||
return m, tea.Batch(cmds...)
|
||||
return m, nil
|
||||
//предохранитель
|
||||
//return m,tea.Batch(cmds...)
|
||||
}
|
||||
//Возвращаем команду собранную из массива команд текущей модели
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// View формирует строку на основе модального окна или текущей модели
|
||||
func (m *Main) View() string {
|
||||
view := m.current.View()
|
||||
if m.overlay != nil {
|
||||
|
|
@ -164,10 +197,14 @@ func (m *Main) View() string {
|
|||
return view
|
||||
}
|
||||
|
||||
// Sugar проверяет поле указанное в сообщение на важность в форме и приложении.
|
||||
func (m *Main) Sugar(sugar types.SugarMsg) {
|
||||
//Если полей с указанной переменной несколько, то выключаем все прочие поля кроме, указанного в сообщении
|
||||
if m.IsMultiField(sugar.VariableName) {
|
||||
m.DeactivateField(sugar.VariableName, sugar.FieldName)
|
||||
}
|
||||
//Если от переменной поля зависит секции или поля,
|
||||
//то вызываем перерасчет значений compute полей и формируем новую форму с учетом прошлого фокуса на форме
|
||||
if IsParent(sugar.VariableName, m.CurrentApplication.Sections) {
|
||||
if sugar.Compute != "" {
|
||||
Recompute(m.CurrentApplication)
|
||||
|
|
@ -176,6 +213,7 @@ func (m *Main) Sugar(sugar types.SugarMsg) {
|
|||
}
|
||||
}
|
||||
|
||||
// IsParent проверяет являться ли указанная переменная условие для поля или секции
|
||||
func IsParent(variableName string, sections []types.Section) bool {
|
||||
for _, section := range sections {
|
||||
for _, parent := range section.Parent {
|
||||
|
|
@ -195,6 +233,7 @@ func IsParent(variableName string, sections []types.Section) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// IsMultiField проверяет переменную на использование несколькими полями.
|
||||
func (m *Main) IsMultiField(variableName string) bool {
|
||||
var count int
|
||||
for _, section := range m.CurrentApplication.Sections {
|
||||
|
|
@ -207,6 +246,7 @@ func (m *Main) IsMultiField(variableName string) bool {
|
|||
return count > 1
|
||||
}
|
||||
|
||||
// DeactivateField отключает все поля использующие указанную переменную, кроме поля с указанным именем
|
||||
func (m *Main) DeactivateField(variableName, fieldName string) {
|
||||
for sectionIdx, section := range m.CurrentApplication.Sections {
|
||||
for fieldIdx, field := range section.Fields {
|
||||
|
|
@ -220,23 +260,3 @@ func (m *Main) DeactivateField(variableName, fieldName string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Main) RecomputeFields() {
|
||||
for i := range m.CurrentApplication.Sections {
|
||||
section := &m.CurrentApplication.Sections[i]
|
||||
for j := range section.Fields {
|
||||
field := §ion.Fields[j]
|
||||
if field.FieldType == "compute" && field.Compute != "" {
|
||||
if field.Variable != nil {
|
||||
continue
|
||||
}
|
||||
value, err := ComputeValue(field.Compute, m.CurrentApplication)
|
||||
if err != nil {
|
||||
value = fmt.Sprintf("Ошибка вычисления: %v", err)
|
||||
}
|
||||
field.Variable.Value = value
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
105
setup/modal.go
105
setup/modal.go
|
|
@ -7,21 +7,20 @@ import (
|
|||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// ModalModel структура описывающая модель отрисовки модального окна
|
||||
type ModalModel struct {
|
||||
modalType types.ModalType
|
||||
title string
|
||||
message string
|
||||
buttons []types.ModalButton
|
||||
focus int
|
||||
width int
|
||||
height int
|
||||
onResult func(result any) tea.Cmd
|
||||
modalType types.ModalType //Тип модального окна
|
||||
title string //Заголовок модального окна
|
||||
message string //Сообщение модального окна
|
||||
buttons []types.ModalButton //Кнопки модального окна
|
||||
focus int //Фокус для отслеживания с каким объектом надо работать
|
||||
width int //Ширина окна
|
||||
height int //Высота окна
|
||||
onResult func(result any) tea.Cmd //Какое действие надо выполнить при нажатии
|
||||
}
|
||||
|
||||
// NewModalModel создает новую модель модального окна на основе переданного сообщения
|
||||
func NewModalModel(msg types.ShowModalMsg, width, height int) *ModalModel {
|
||||
if len(msg.ModalButtons) == 0 {
|
||||
msg.ModalButtons = []types.ModalButton{{Text: "Ok", Value: true}}
|
||||
}
|
||||
return &ModalModel{
|
||||
modalType: msg.Type,
|
||||
title: msg.Title,
|
||||
|
|
@ -38,32 +37,46 @@ func (m *ModalModel) Init() tea.Cmd {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Update отслеживает действия с модальным окном
|
||||
func (m *ModalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
//Отслеживаем и записываем новые размеры окна
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
//Отслеживаем нажатие клавиш модального окна
|
||||
switch msg.String() {
|
||||
case "left", "h", "shift+tab":
|
||||
case "left", "shift+tab":
|
||||
//Обработчик нажатия клавиш left и shift+tab нужен для перевода фокуса на прошлую кнопку.
|
||||
//Уменьшаем фокус, если фокус стал меньше 0, значит он вышел за пределы кнопок,
|
||||
//переводим его на последнюю кнопку.
|
||||
m.focus--
|
||||
if m.focus < 0 {
|
||||
m.focus = len(m.buttons) - 1
|
||||
}
|
||||
case "right", "l", "tab":
|
||||
case "right", "tab":
|
||||
//Обработчик нажатия клавиш right и tab нужен для перевода фокуса на следующую кнопку.
|
||||
//Увеличиваем фокус, если фокус стал равен количество кнопок, значит он вышел за их пределы,
|
||||
//переводим его на первую позицию, то есть на 0
|
||||
m.focus++
|
||||
if m.focus >= len(m.buttons) {
|
||||
m.focus = 0
|
||||
}
|
||||
case "enter", " ":
|
||||
//вытаскиваем значение кнопки
|
||||
//Обработчик клавиши enter нужен для подтверждения кнопки.
|
||||
//Получаем значение кнопки, а после если у модального окна прописана функция,
|
||||
//то передаем значение кнопки в функцию модального окна.
|
||||
result := m.buttons[m.focus].Value
|
||||
var cmd tea.Cmd
|
||||
if m.onResult != nil { //если функция кнопки не пустая, мы передаем в функцию значение кнопки
|
||||
if m.onResult != nil {
|
||||
cmd = m.onResult(result)
|
||||
}
|
||||
return m, tea.Batch( //объединяем 2 действия, функцию кнопки и закрытие модального окна
|
||||
//Возвращаем единую команду составленную из двух,
|
||||
//результата выполнения функции модального окна,
|
||||
//функцию возвращения сообщения о закрытии модального окна.
|
||||
return m, tea.Batch(
|
||||
cmd,
|
||||
func() tea.Msg { return types.CloseModalMsg{} },
|
||||
)
|
||||
|
|
@ -72,13 +85,14 @@ func (m *ModalModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
// View формирует строку для вывода модального окна и её контента
|
||||
func (m *ModalModel) View() string {
|
||||
|
||||
//Данные о размерах контейнера
|
||||
containerWidth, containerHeight, _, _ := ui.ContentSizes(m.width, m.height)
|
||||
|
||||
//Модальное окно не должно занимать все пространство, поэтому получаем минимально допустимые размеры
|
||||
modalWidth := min(containerWidth, ui.ModalMaxWidth)
|
||||
modalHeight := min(containerHeight, ui.ModalMaxHeight)
|
||||
|
||||
//Формируем просто сообщение и кнопки с учетом фокуса
|
||||
message := lipgloss.NewStyle().Render(m.message)
|
||||
var buttons []string
|
||||
for i, button := range m.buttons {
|
||||
|
|
@ -88,12 +102,14 @@ func (m *ModalModel) View() string {
|
|||
}
|
||||
buttons = append(buttons, style.Render(button.Text))
|
||||
}
|
||||
//Строка кнопок
|
||||
buttonRow := lipgloss.JoinHorizontal(lipgloss.Center, buttons...)
|
||||
//Тело контента модального окна
|
||||
modalBody := lipgloss.JoinVertical(lipgloss.Center, message, "", buttonRow)
|
||||
|
||||
//В зависимости от типа модального окна разные цвета
|
||||
var borderColor lipgloss.Color
|
||||
var modalTitleStyle lipgloss.Style
|
||||
|
||||
switch m.modalType {
|
||||
case types.ModalError:
|
||||
borderColor = ui.ColorError
|
||||
|
|
@ -105,33 +121,19 @@ func (m *ModalModel) View() string {
|
|||
borderColor = ui.ColorAccent
|
||||
modalTitleStyle = ui.ModalTitleDefault
|
||||
}
|
||||
//
|
||||
//switch m.modalType {
|
||||
//case types.ModalError:
|
||||
// modalStyle = ui.ModalBorderError
|
||||
// modalTitleStyle = ui.ModalTitleError
|
||||
//case types.ModalWarning, types.ModalConfirm:
|
||||
// modalStyle = ui.ModalBorderWarning
|
||||
// modalTitleStyle = ui.ModalTitleWarning
|
||||
//default:
|
||||
// modalStyle = ui.ModalBorderInfo
|
||||
// modalTitleStyle = ui.ModalTitleDefault
|
||||
//}
|
||||
var modalView string
|
||||
//Формируем модальный контейнер с контентом внутри и указанным цветом границ.
|
||||
modalView := ui.ModalContainer.
|
||||
Width(modalWidth).
|
||||
Height(modalHeight).
|
||||
Align(lipgloss.Center).
|
||||
Border(lipgloss.DoubleBorder()).
|
||||
BorderForeground(borderColor).
|
||||
Render(modalBody)
|
||||
|
||||
symbolToRepeat := "*"
|
||||
//Если у модального окна есть заголовок, то в modalView записываем новое значение,
|
||||
//ставя заголовок сверху модального контейнера
|
||||
if m.title != "" {
|
||||
modalView = ui.ModalContainer.
|
||||
Width(modalWidth).
|
||||
Height(modalHeight).
|
||||
Align(lipgloss.Center, lipgloss.Center).
|
||||
Border(lipgloss.DoubleBorder()).
|
||||
BorderForeground(borderColor).
|
||||
Render(modalBody)
|
||||
//modalView = ui.ModalContainer.
|
||||
// Width(modalWidth).
|
||||
// Height(modalHeight).
|
||||
// BorderForeground(borderColor).
|
||||
// Render(modalBody)
|
||||
//repeatCount := modalWidth - utf8.RuneCountInString(m.title)
|
||||
//if repeatCount < 0 {
|
||||
// repeatCount = 0
|
||||
|
|
@ -139,19 +141,6 @@ func (m *ModalModel) View() string {
|
|||
//symbolFill := strings.Repeat(symbolToRepeat, repeatCount/2)
|
||||
//modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(symbolFill+m.title+symbolFill), modalView)
|
||||
modalView = lipgloss.JoinVertical(lipgloss.Center, modalTitleStyle.Render(m.title), modalView)
|
||||
} else {
|
||||
modalView = ui.ModalContainer.
|
||||
Width(modalWidth).
|
||||
Height(modalHeight).
|
||||
BorderStyle(lipgloss.ASCIIBorder()).
|
||||
BorderForeground(borderColor).
|
||||
Render(modalBody)
|
||||
//modalView = modalStyle.
|
||||
// Width(modalWidth).
|
||||
// Height(modalHeight).
|
||||
// Padding(1, 3).
|
||||
// Border(lipgloss.DoubleBorder()).
|
||||
// Render(modalBody)
|
||||
}
|
||||
return lipgloss.Place(m.width, m.height,
|
||||
lipgloss.Center, lipgloss.Center,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,14 @@ func ContentSizes(screenWidth, screenHeight int) (containerWidth, containerHeigh
|
|||
|
||||
// Используемые цвета
|
||||
var (
|
||||
// ColorForeground = lipgloss.Color("#c0caf5")
|
||||
// ColorBackground = lipgloss.Color("#1a1b26")
|
||||
// ColorAccent = lipgloss.Color("#7aa2f7")
|
||||
// ColorSuccess = lipgloss.Color("#9ece6a")
|
||||
// ColorError = lipgloss.Color("#f7768e")
|
||||
// ColorWarning = lipgloss.Color("#e0af68")
|
||||
// ColorInfo = lipgloss.Color("#7dcfff")
|
||||
|
||||
ColorFg = lipgloss.Color("#eeeeee")
|
||||
ColorBg = lipgloss.Color("#0d1117")
|
||||
ColorAccent = lipgloss.Color("#a6e3a1")
|
||||
|
|
@ -77,6 +85,17 @@ var AppContainer = lipgloss.NewStyle().
|
|||
|
||||
// Стили для списка приложений
|
||||
var (
|
||||
ListContainerStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(ColorPurple).
|
||||
AlignVertical(lipgloss.Top)
|
||||
|
||||
ListDescriptionContainerStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(ColorError).
|
||||
Foreground(ColorAccent2).
|
||||
AlignVertical(lipgloss.Top)
|
||||
|
||||
ListTitleStyle = lipgloss.NewStyle().Foreground(ColorPurple).Italic(true).Bold(true)
|
||||
ListSelectedStyle = lipgloss.NewStyle().Foreground(ColorError).Bold(true)
|
||||
ListDescriptionStyle = lipgloss.NewStyle().Foreground(ColorAccent2)
|
||||
|
|
@ -124,12 +143,11 @@ var (
|
|||
Bold(true).
|
||||
Align(lipgloss.Center).
|
||||
MarginBottom(1).
|
||||
Padding(1, 2)
|
||||
Padding(1, 0)
|
||||
|
||||
FinalFooter = lipgloss.NewStyle().
|
||||
Foreground(ColorGrey).
|
||||
MarginTop(1).
|
||||
Padding(1, 0).
|
||||
Align(lipgloss.Center)
|
||||
|
||||
FinalTableBorder = lipgloss.NewStyle().
|
||||
|
|
@ -154,12 +172,6 @@ var (
|
|||
ModalContainer = lipgloss.NewStyle().
|
||||
Border(lipgloss.DoubleBorder()).
|
||||
Align(lipgloss.Center, lipgloss.Center)
|
||||
//Padding(ApplicationVerticalOffset, ApplicationHorizontalOffset)
|
||||
|
||||
ModalBorderError = lipgloss.NewStyle().BorderForeground(ColorError)
|
||||
ModalBorderWarning = lipgloss.NewStyle().BorderForeground(ColorWarning)
|
||||
ModalBorderConfirm = lipgloss.NewStyle().BorderForeground(ColorPurple)
|
||||
ModalBorderInfo = lipgloss.NewStyle().BorderForeground(ColorAccent)
|
||||
|
||||
ModalTitleError = lipgloss.NewStyle().BorderForeground(ColorError).Bold(true)
|
||||
ModalTitleWarning = lipgloss.NewStyle().BorderForeground(ColorWarning).Bold(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue