From d715330dedbec0b3fac1cc61f2d35dc9fcb62377 Mon Sep 17 00:00:00 2001 From: pxor Date: Fri, 28 Feb 2025 19:29:41 +0200 Subject: [PATCH 01/17] WIP: Start adding support for upload/download of trace files Currently there is a GUI for the upload button When clicked, it zips the given trace with a random password encryption and uploads it to the provided web api root --- src/common/common_trace_index.nim | 5 ++++- src/common/common_types.nim | 4 ++++ src/config/default_config.yaml | 2 +- src/frontend/index.nim | 25 +++++++++++++++++++++++++ src/frontend/ui/welcome_screen.nim | 14 ++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/common/common_trace_index.nim b/src/common/common_trace_index.nim index b5ad1901a..8ffc2ec9a 100644 --- a/src/common/common_trace_index.nim +++ b/src/common/common_trace_index.nim @@ -39,7 +39,10 @@ const SQL_CREATE_TABLE_STATEMENTS = @[ exitCode integer, calltrace integer, calltraceMode string, - date text);""", + date text, + downloadId string, + controlId string, + key string);""", """CREATE TABLE IF NOT EXISTS trace_values ( id integer, maxTraceID integer, diff --git a/src/common/common_types.nim b/src/common/common_types.nim index 0640b8898..b50492007 100644 --- a/src/common/common_types.nim +++ b/src/common/common_types.nim @@ -1341,6 +1341,10 @@ type BugReportArg* = object ## BugReport arg title*: langstring description*: langstring + + UploadTraceArg* = object + trace*: Trace + programName*: langstring DbEventKind* {.pure.} = enum Record, Trace, History diff --git a/src/config/default_config.yaml b/src/config/default_config.yaml index f1c47253a..3991e48be 100644 --- a/src/config/default_config.yaml +++ b/src/config/default_config.yaml @@ -54,7 +54,7 @@ defaultBuild: "" showMinimap: true # for now local setup -webApiRoot: http://100.87.206.30:57103/api/codetracer +webApiRoot: http://localhost:55500/api/codetracer/anon # # you can use KEY+OTHER # # use PageUp, PageDown, CTRL, ALT, SHIFT diff --git a/src/frontend/index.nim b/src/frontend/index.nim index 4006a8b5b..be2f67ba0 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -800,6 +800,27 @@ proc onSearchProgram(sender: js, query: cstring) {.async.} = proc onLoadStepLines(sender: js, response: LoadStepLinesArg) {.async.} = discard debugger.loadStepLines(response) +proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = + let res = await readProcessOutput( + codetracerExe.cstring, + @[ + j"upload", + j"--trace-folder=" & response.trace.outputFolder + ] + ) + + if res.isOk: + echo "### TODO: STORE THE PASSWORD AND DOWNLOAD ID" + let commandList = res.v.split("\n") + let password = commandList[0] + let message = commandList[^1] + echo "### PASSWORD = ", password + echo "### MESSAGE = ", message + +proc onDownloadTraceFile(sender: js) {.async.} = + # TODO: Implement download trace from downloadId and key + discard + proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( codetracerExe.cstring, @@ -1298,6 +1319,10 @@ proc configureIpcMain = "show-in-debug-instance" "send-bug-report-and-logs" + # Upload/Download + "upload-trace-file" + "download-trace-file" + "restart" # "debug-gdb" diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index 288334ad6..257cd5d07 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -3,6 +3,13 @@ import ../../ct/version, ui_imports, ../types +proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) = + self.data.ipc.send "CODETRACER::upload-trace-file", + UploadTraceArg( + trace: trace, + programName: trace.program + ) + proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = buildHtml( tdiv( @@ -26,6 +33,13 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = separateBar() span(class = "recent-trace-title-content"): text limitedProgramName # TODO: tippy + tdiv(class = "recent-trace-buttons"): + span( + onclick = proc(ev: Event, tg: VNode) = + ev.stopPropagation() + self.uploadTrace(trace) + ): + text "^" # tdiv(class = "recent-trace-info"): # tdiv(class = "recent-trace-date"): # text trace.date From c50beec34295a92b31992a9749b583f49f77fd1a Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 4 Mar 2025 12:52:57 +0200 Subject: [PATCH 02/17] WIP: Store the generated password, downloadId, controlId and expire time to the database file --- examples/noir_space_ship.zip | Bin 0 -> 2832 bytes src/common/common_trace_index.nim | 3 ++- src/common/trace_index.nim | 28 ++++++++++++++++++++++++++++ src/frontend/index.nim | 8 -------- 4 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 examples/noir_space_ship.zip diff --git a/examples/noir_space_ship.zip b/examples/noir_space_ship.zip new file mode 100644 index 0000000000000000000000000000000000000000..7a77030f96c822415e026234c6d29438fa3d196b GIT binary patch literal 2832 zcmbVO2{_d28XsoHK9(9xj3o^YWti+C#=garvUF%n%wdMHjA4+nUm<%<8AO*5NtWs= zTT-N4Iwi`^WGu-zxkS;K>D+U!Q`Ga^@Be)N|M!2M@A(85Cx004Qjw;emm4*CZ^(Z-FATz@~s$XACqQEBZjic{dLX#)vH&fKHs)S3IO4VGGf=5tF zb{m}q9S*|EulSdFPrZ^!P>JViLRYJmUYoUNFw%rh2%3A{caf5xYA_~dgvDW7XC~zP zYa+{Enbp}1%?qcG?s;bS34pQVDF@^D;{TjuVpAY0evhh6bZNuMY zFg*auBp-b|x3WJ5C+d=!3TEDHM(OMA6XLOmHn`HUv5zZ%znS2zrCv&eQStI5w5dj? zpx(k9;T(_X78}h`sS1W!KS>w`g?lDyD*p7WT!AJ+b8eUY3&K+s%)DRk6c0++GDM95G z=51mQYxSB=1)@c|99Pd>OZBZD?>jO-J0r=-1ld%EunRW;V8iphM3>nn6WEm#@9+Ji z+v`m78`KcpxQq>kc4faL)TWu*SmQL7fwx6IVr(%aeX#*r7UTBr4=oEzJKSiKSL5H9 z&AC#0hC^0{R6^f#M}X}QqoKn)l11|I=%%FuvHNH`_Cl$TV%`Swr>j^ePPD63Xz5 zpstK*7z$gO8A~rE8&#hw+ogiiHgvSf(n*~1!CEk~(MVdy^>}H7!KhyNMSS5d39J5` zMS)^<0~qZA1z>^D!nU;T+2_+C?2C>5<%`) zt&jI_-)km!t1Of3Rtp6^zQiBK)c&d5UjUr9JH(%_4pNir_Qb{@cdW#{H(M?Nc1w5n zyVPBf_Y6u}d$hi@fqhnUe`A}&?x^6_lEZJcdfd8L!;d$lY2FQu%YD9 zJKfKr*LA^XUM~o9wXY|MzPl)4Y6-iW?|=)fh$N>yDS{x=3!T~tmp{Spow*q$WjQ#_ z4Jw}OKD4IG(tjbThLF)MP}uPlh%_RdbZ3rPg?V4xNX3`MlbE=`^{_tuqLR-SGQ6*%zR?+_OBG!N-4!gfyQ`t2FWZ;4aL zfkY*KaUX@)p^E*Aeac|<7Zc<7EEL))vGNEx{Q13>$g8G=T-w?R`40xX^I>Z=y%$hi z!doa&e@wlo3OS<$v%+>E7Z%c4HO#&>f6~VZRm#|dx-1i812esJh1&trR*QSwc%Jo) zgwF-{e{Oo&vzo#5X)CPGXhIg`hbCGE56)?JphsUDb8=E3`$DW5TZjk>a&nT38w%V` z#=tmj>&uu&b3Q*5HDCbOAA;r&)ITN7=5{Xy_#bLXtnC)4-w`<5;BW4?(tpFZ=K9v_ z?@5TS3~)$)t?r 0: + return res[0][0] + return "" + proc recordTrace*( id: int, program: string, diff --git a/src/frontend/index.nim b/src/frontend/index.nim index be2f67ba0..e750497cb 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -809,14 +809,6 @@ proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = ] ) - if res.isOk: - echo "### TODO: STORE THE PASSWORD AND DOWNLOAD ID" - let commandList = res.v.split("\n") - let password = commandList[0] - let message = commandList[^1] - echo "### PASSWORD = ", password - echo "### MESSAGE = ", message - proc onDownloadTraceFile(sender: js) {.async.} = # TODO: Implement download trace from downloadId and key discard From b845999e7a0ee49ae1d06912af3bcb125e387f72 Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 4 Mar 2025 18:10:06 +0200 Subject: [PATCH 03/17] feat: Improve the password generator --- .gitmodules | 3 +++ libs/nimcrypto | 1 + nim.cfg | 1 + 3 files changed, 5 insertions(+) create mode 160000 libs/nimcrypto diff --git a/.gitmodules b/.gitmodules index 371e04521..a86f8317c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -65,3 +65,6 @@ [submodule "libs/codetracer-python-recorder"] path = libs/codetracer-python-recorder url = https://github.com/metacraft-labs/codetracer-python-recorder.git +[submodule "libs/nimcrypto"] + path = libs/nimcrypto + url = https://github.com/cheatfate/nimcrypto.git diff --git a/libs/nimcrypto b/libs/nimcrypto new file mode 160000 index 000000000..69eec0375 --- /dev/null +++ b/libs/nimcrypto @@ -0,0 +1 @@ +Subproject commit 69eec0375dd146aede41f920c702c531bfe89c6b diff --git a/nim.cfg b/nim.cfg index 97e65ae68..7ea617a51 100644 --- a/nim.cfg +++ b/nim.cfg @@ -23,5 +23,6 @@ path:"libs/chronos" path:"libs/parsetoml/src" path:"libs/nim-result" path:"libs/nim-confutils" +path:"libs/nimcrypto" gcc.options.debug = "-O0 -g3" From 93ac7faf3a89e92ec27f23a46f7775abae10777c Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 4 Mar 2025 18:16:58 +0200 Subject: [PATCH 04/17] build: Add 'zip' and 'unzip' In the nim-lan/zip library password encryption is not supported so we are currently using the standard zip and unzip packages --- examples/noir_space_ship.zip | Bin 2832 -> 0 bytes nix/shells/main.nix | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 examples/noir_space_ship.zip diff --git a/examples/noir_space_ship.zip b/examples/noir_space_ship.zip deleted file mode 100644 index 7a77030f96c822415e026234c6d29438fa3d196b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2832 zcmbVO2{_d28XsoHK9(9xj3o^YWti+C#=garvUF%n%wdMHjA4+nUm<%<8AO*5NtWs= zTT-N4Iwi`^WGu-zxkS;K>D+U!Q`Ga^@Be)N|M!2M@A(85Cx004Qjw;emm4*CZ^(Z-FATz@~s$XACqQEBZjic{dLX#)vH&fKHs)S3IO4VGGf=5tF zb{m}q9S*|EulSdFPrZ^!P>JViLRYJmUYoUNFw%rh2%3A{caf5xYA_~dgvDW7XC~zP zYa+{Enbp}1%?qcG?s;bS34pQVDF@^D;{TjuVpAY0evhh6bZNuMY zFg*auBp-b|x3WJ5C+d=!3TEDHM(OMA6XLOmHn`HUv5zZ%znS2zrCv&eQStI5w5dj? zpx(k9;T(_X78}h`sS1W!KS>w`g?lDyD*p7WT!AJ+b8eUY3&K+s%)DRk6c0++GDM95G z=51mQYxSB=1)@c|99Pd>OZBZD?>jO-J0r=-1ld%EunRW;V8iphM3>nn6WEm#@9+Ji z+v`m78`KcpxQq>kc4faL)TWu*SmQL7fwx6IVr(%aeX#*r7UTBr4=oEzJKSiKSL5H9 z&AC#0hC^0{R6^f#M}X}QqoKn)l11|I=%%FuvHNH`_Cl$TV%`Swr>j^ePPD63Xz5 zpstK*7z$gO8A~rE8&#hw+ogiiHgvSf(n*~1!CEk~(MVdy^>}H7!KhyNMSS5d39J5` zMS)^<0~qZA1z>^D!nU;T+2_+C?2C>5<%`) zt&jI_-)km!t1Of3Rtp6^zQiBK)c&d5UjUr9JH(%_4pNir_Qb{@cdW#{H(M?Nc1w5n zyVPBf_Y6u}d$hi@fqhnUe`A}&?x^6_lEZJcdfd8L!;d$lY2FQu%YD9 zJKfKr*LA^XUM~o9wXY|MzPl)4Y6-iW?|=)fh$N>yDS{x=3!T~tmp{Spow*q$WjQ#_ z4Jw}OKD4IG(tjbThLF)MP}uPlh%_RdbZ3rPg?V4xNX3`MlbE=`^{_tuqLR-SGQ6*%zR?+_OBG!N-4!gfyQ`t2FWZ;4aL zfkY*KaUX@)p^E*Aeac|<7Zc<7EEL))vGNEx{Q13>$g8G=T-w?R`40xX^I>Z=y%$hi z!doa&e@wlo3OS<$v%+>E7Z%c4HO#&>f6~VZRm#|dx-1i812esJh1&trR*QSwc%Jo) zgwF-{e{Oo&vzo#5X)CPGXhIg`hbCGE56)?JphsUDb8=E3`$DW5TZjk>a&nT38w%V` z#=tmj>&uu&b3Q*5HDCbOAA;r&)ITN7=5{Xy_#bLXtnC)4-w`<5;BW4?(tpFZ=K9v_ z?@5TS3~)$)t?r Date: Wed, 5 Mar 2025 16:43:14 +0200 Subject: [PATCH 05/17] feat: Use nim-lang/zip Still need to encrypt the file and set up the codetracer web api --- .gitmodules | 3 +++ libs/zip | 1 + nim.cfg | 1 + nix/shells/main.nix | 7 ++++--- 4 files changed, 9 insertions(+), 3 deletions(-) create mode 160000 libs/zip diff --git a/.gitmodules b/.gitmodules index a86f8317c..be99304b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -68,3 +68,6 @@ [submodule "libs/nimcrypto"] path = libs/nimcrypto url = https://github.com/cheatfate/nimcrypto.git +[submodule "libs/zip"] + path = libs/zip + url = git@github.com:nim-lang/zip.git diff --git a/libs/zip b/libs/zip new file mode 160000 index 000000000..06f5b0a07 --- /dev/null +++ b/libs/zip @@ -0,0 +1 @@ +Subproject commit 06f5b0a0767b14c7595ed168611782be69e61543 diff --git a/nim.cfg b/nim.cfg index 7ea617a51..224d88e69 100644 --- a/nim.cfg +++ b/nim.cfg @@ -24,5 +24,6 @@ path:"libs/parsetoml/src" path:"libs/nim-result" path:"libs/nim-confutils" path:"libs/nimcrypto" +path:"libs/zip" gcc.options.debug = "-O0 -g3" diff --git a/nix/shells/main.nix b/nix/shells/main.nix index f9e2043f3..725e5c4f4 100644 --- a/nix/shells/main.nix +++ b/nix/shells/main.nix @@ -82,8 +82,9 @@ in dash lesspipe unixtools.killall - zip - unzip + # zip + # unzip + libzip # curl # for pgrep at least @@ -150,7 +151,7 @@ in # copied case for libstdc++.so (needed by better-sqlite3) from # https://discourse.nixos.org/t/what-package-provides-libstdc-so-6/18707/4: # gcc.cc.lib .. - export CT_LD_LIBRARY_PATH="${sqlite.out}/lib/:${pcre.out}/lib:${glib.out}/lib:${openssl.out}/lib:${gcc.cc.lib}/lib"; + export CT_LD_LIBRARY_PATH="${sqlite.out}/lib/:${pcre.out}/lib:${glib.out}/lib:${openssl.out}/lib:${gcc.cc.lib}/lib:${libzip.out}/lib"; export RUST_LOG=info From 672f71c3f6c2a038f995c945115e627daaa5ec2c Mon Sep 17 00:00:00 2001 From: pxor Date: Thu, 6 Mar 2025 16:57:16 +0200 Subject: [PATCH 06/17] feat: Add download of encrypted zip file, unzip and import trace through :: --- src/common/common_trace_index.nim | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/common/common_trace_index.nim b/src/common/common_trace_index.nim index 42043c43d..62fd095a9 100644 --- a/src/common/common_trace_index.nim +++ b/src/common/common_trace_index.nim @@ -40,10 +40,9 @@ const SQL_CREATE_TABLE_STATEMENTS = @[ calltrace integer, calltraceMode string, date text, - downloadId string, - controlId string, - passwordKey string, - expireTime integer);""", + remoteShareDownloadId string, + remoteShareControlId string, + remoteShareExpireTime integer);""", """CREATE TABLE IF NOT EXISTS trace_values ( id integer, maxTraceID integer, @@ -60,6 +59,9 @@ const SQL_INITIAL_INSERT_STATEMENTS = @[ const SQL_ALTER_TABLE_STATEMENTS: seq[string] = @[ # example: adding a new column """ALTER TABLE traces ADD COLUMN calltraceMode text;""", + """ALTER TABLE traces ADD COLUMN remoteShareDownloadId text;""", + """ALTER TABLE traces ADD COLUMN remoteShareControlId text;""", + """ALTER TABLE traces ADD COLUMN remoteShareExpireTime text;""", """ALTER TABLE traces RENAME COLUMN callgraph TO calltrace""" # """ALTER TABLE traces ADD COLUMN love integer;""" ] From de6b9c12a8bc6ca3ecbe86edf85b802a869e12a6 Mon Sep 17 00:00:00 2001 From: pxor Date: Thu, 6 Mar 2025 17:49:09 +0200 Subject: [PATCH 07/17] feat: Add UI download handling for welcome screen 'open online trace' and auto open on download complete --- src/frontend/index.nim | 8 ++-- src/frontend/types.nim | 4 ++ src/frontend/ui/welcome_screen.nim | 64 ++++++++++++++++++++++++++++-- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/frontend/index.nim b/src/frontend/index.nim index e750497cb..343b5b3eb 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -809,9 +809,11 @@ proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = ] ) -proc onDownloadTraceFile(sender: js) {.async.} = - # TODO: Implement download trace from downloadId and key - discard +proc onDownloadTraceFile(sender: js, response: jsobject(downloadId=seq[cstring])) {.async.} = + let res = await readProcessOutput( + codetracerExe.cstring, + @[j"download"].concat(response.downloadId) + ) proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( diff --git a/src/frontend/types.nim b/src/frontend/types.nim index 2251ce2f0..ee6e9ec0e 100644 --- a/src/frontend/types.nim +++ b/src/frontend/types.nim @@ -1094,6 +1094,8 @@ type kind*: RecordStatusKind errorMessage*: cstring + NewDownloadRecord* = ref object + args*: seq[cstring] NewTraceRecord* = ref object kit*: cstring @@ -1131,7 +1133,9 @@ type options*: seq[WelcomeScreenOption] welcomeScreen*: bool newRecordScreen*: bool + openOnlineTrace*: bool newRecord*: NewTraceRecord + newDownload*: NewDownloadRecord loading*: bool loadingTrace*: Trace diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index 257cd5d07..dfd8acaec 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -219,6 +219,48 @@ proc prepareArgs(self: WelcomeScreenComponent): seq[cstring] = return args.concat(self.newRecord.args) +proc onlineFormView(self: WelcomeScreenComponent): VNode = + buildHtml( + tdiv(class = "new-record-form") + ): + renderInputRow( + "args", + "Download ID with password", + "", + proc(ev: Event, tg: VNode) = discard, + proc(ev: Event, tg: VNode) = self.newDownload.args = ev.target.value.split(" "), + hasButton = false, + inputText = self.newDownload.args.join(j" ") + ) + tdiv(class = "new-record-form-row"): + button( + class = "cancel-button", + onclick = proc(ev: Event, tg: VNode) = + ev.preventDefault() + self.welcomeScreen = true + self.openOnlineTrace = false + self.newDownload = nil + ): + text "Back" + button( + class = "confirmation-button", + onclick = proc(ev: Event, tg: VNode) = + ev.preventDefault() + self.loading = true + # TODO: Implement progress bar + # self.newRecord.status.kind = InProgress + # let workDir = if self.newRecord.workDir.isNil or self.newRecord.workDir.len == 0: + # jsUndefined + # else: + # cast[JsObject](self.newRecord.workDir) + self.data.ipc.send( + "CODETRACER::download-trace-file", js{ + downloadId: concat(self.newDownload.args), + } + ) + ): + text "Download" + proc newRecordFormView(self: WelcomeScreenComponent): VNode = buildHtml( tdiv(class = "new-record-form") @@ -352,6 +394,16 @@ proc newRecordView(self: WelcomeScreenComponent): VNode = text "Start Debugger" newRecordFormView(self) +proc onlineTraceView(self: WelcomeScreenComponent): VNode = + buildHtml( + tdiv(class = "new-record-screen") + ): + tdiv(class = "new-record-screen-content"): + tdiv(class = "welcome-logo") + tdiv(class = "new-record-title"): + text "Start Debugger" + onlineFormView(self) + proc loadInitialOptions(self: WelcomeScreenComponent) = self.options = @[ WelcomeScreenOption( @@ -386,8 +438,12 @@ proc loadInitialOptions(self: WelcomeScreenComponent) = ), WelcomeScreenOption( name: "Open online trace", - inactive: true, - command: proc = discard + command: proc = + self.openOnlineTrace = true + self.welcomeScreen = false + self.newDownload = NewDownloadRecord( + args: @[] + ) ), WelcomeScreenOption( name: "CodeTracer shell", @@ -435,13 +491,15 @@ method render*(self: WelcomeScreenComponent): VNode = self.loadInitialOptions() buildHtml(tdiv()): - if self.welcomeScreen or self.newRecordScreen: + if self.welcomeScreen or self.newRecordScreen or self.openOnlineTrace: tdiv(class = "welcome-screen-wrapper"): windowMenu(data, true) if self.welcomeScreen: welcomeScreenView(self) elif self.newRecordScreen: newRecordView(self) + elif self.openOnlineTrace: + onlineTraceView(self) if self.loading: loadingOverlay(self) From 308748fefc93792b74741b6bf80960417eaa2c91 Mon Sep 17 00:00:00 2001 From: pxor Date: Thu, 6 Mar 2025 18:33:00 +0200 Subject: [PATCH 08/17] WIP: Add auto start when open online trace from UI --- src/frontend/index.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/frontend/index.nim b/src/frontend/index.nim index 343b5b3eb..29230abe0 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -27,6 +27,8 @@ data.start = now() var close = false proc showOpenDialog(dialog: JsObject, browserWindow: JsObject, options: JsObject): Future[JsObject] {.importjs: "#.showOpenDialog(#,#)".} +proc loadExistingRecord(traceId: int) {.async.} +proc prepareForLoadingTrace(traceId: int, pid: int) {.async.} proc isCtInstalled: bool @@ -815,6 +817,10 @@ proc onDownloadTraceFile(sender: js, response: jsobject(downloadId=seq[cstring]) @[j"download"].concat(response.downloadId) ) + if res.isOk: + await prepareForLoadingTrace(parseInt($res.v.trim()), nodeProcess.pid.to(int)) + await loadExistingRecord(parseInt($res.v.trim())) + proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( codetracerExe.cstring, From c85585d66dc9ec32d4f8f7d2361f500582606758 Mon Sep 17 00:00:00 2001 From: pxor Date: Fri, 7 Mar 2025 12:01:12 +0200 Subject: [PATCH 09/17] fix: Auto detect trace language on download and some other minor changes --- src/common/common_trace_index.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/common_trace_index.nim b/src/common/common_trace_index.nim index 62fd095a9..b8a8bc499 100644 --- a/src/common/common_trace_index.nim +++ b/src/common/common_trace_index.nim @@ -59,9 +59,9 @@ const SQL_INITIAL_INSERT_STATEMENTS = @[ const SQL_ALTER_TABLE_STATEMENTS: seq[string] = @[ # example: adding a new column """ALTER TABLE traces ADD COLUMN calltraceMode text;""", + """ALTER TABLE traces RENAME COLUMN callgraph TO calltrace""", """ALTER TABLE traces ADD COLUMN remoteShareDownloadId text;""", """ALTER TABLE traces ADD COLUMN remoteShareControlId text;""", - """ALTER TABLE traces ADD COLUMN remoteShareExpireTime text;""", - """ALTER TABLE traces RENAME COLUMN callgraph TO calltrace""" + """ALTER TABLE traces ADD COLUMN remoteShareExpireTime text;""" # """ALTER TABLE traces ADD COLUMN love integer;""" ] From 9c8c91b70d76dccec44ae7f875324815e3d35bab Mon Sep 17 00:00:00 2001 From: pxor Date: Fri, 7 Mar 2025 12:05:36 +0200 Subject: [PATCH 10/17] fix: Sql table columns --- src/common/common_trace_index.nim | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/common/common_trace_index.nim b/src/common/common_trace_index.nim index b8a8bc499..58004444e 100644 --- a/src/common/common_trace_index.nim +++ b/src/common/common_trace_index.nim @@ -39,10 +39,7 @@ const SQL_CREATE_TABLE_STATEMENTS = @[ exitCode integer, calltrace integer, calltraceMode string, - date text, - remoteShareDownloadId string, - remoteShareControlId string, - remoteShareExpireTime integer);""", + date text);""", """CREATE TABLE IF NOT EXISTS trace_values ( id integer, maxTraceID integer, @@ -58,8 +55,6 @@ const SQL_INITIAL_INSERT_STATEMENTS = @[ const SQL_ALTER_TABLE_STATEMENTS: seq[string] = @[ # example: adding a new column - """ALTER TABLE traces ADD COLUMN calltraceMode text;""", - """ALTER TABLE traces RENAME COLUMN callgraph TO calltrace""", """ALTER TABLE traces ADD COLUMN remoteShareDownloadId text;""", """ALTER TABLE traces ADD COLUMN remoteShareControlId text;""", """ALTER TABLE traces ADD COLUMN remoteShareExpireTime text;""" From 7f5b080e670eff3c84e4fefb85255456234727ad Mon Sep 17 00:00:00 2001 From: pxor Date: Fri, 7 Mar 2025 13:58:58 +0200 Subject: [PATCH 11/17] fix: Auto start on download complete of online trace and some minor fixes --- src/frontend/index.nim | 9 +++++---- src/frontend/ui/editor.nim | 1 + src/frontend/ui/welcome_screen.nim | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/frontend/index.nim b/src/frontend/index.nim index 29230abe0..8a4faaf11 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -811,15 +811,16 @@ proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = ] ) -proc onDownloadTraceFile(sender: js, response: jsobject(downloadId=seq[cstring])) {.async.} = +proc onDownloadTraceFile(sender: js, response: jsobject(downloadKey=seq[cstring])) {.async.} = let res = await readProcessOutput( codetracerExe.cstring, - @[j"download"].concat(response.downloadId) + @[j"download"].concat(response.downloadKey) ) if res.isOk: - await prepareForLoadingTrace(parseInt($res.v.trim()), nodeProcess.pid.to(int)) - await loadExistingRecord(parseInt($res.v.trim())) + let traceId = parseInt($res.v.trim()) + await prepareForLoadingTrace(traceId, nodeProcess.pid.to(int)) + await loadExistingRecord(traceId) proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( diff --git a/src/frontend/ui/editor.nim b/src/frontend/ui/editor.nim index c5fcc141a..4653674dc 100644 --- a/src/frontend/ui/editor.nim +++ b/src/frontend/ui/editor.nim @@ -314,6 +314,7 @@ proc styleLines(self: EditorViewComponent, editor: MonacoEditor, lines: seq[Mona self.data.ui.welcomeScreen.loading = false self.data.ui.welcomeScreen.welcomeScreen = false self.data.ui.welcomeScreen.newRecordScreen = false + self.data.ui.welcomeScreen.openOnlineTrace = false proc lineActionClick(self: EditorViewComponent, tabInfo: TabInfo, line: js) = var element = line diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index dfd8acaec..eb304cf77 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -3,6 +3,8 @@ import ../../ct/version, ui_imports, ../types +const PROGRAM_NAME_LIMIT = 45 + proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) = self.data.ipc.send "CODETRACER::upload-trace-file", UploadTraceArg( @@ -21,7 +23,7 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = self.data.ipc.send "CODETRACER::load-recent-trace", js{ traceId: trace.id } ) ): - let programLimitName = 45 + let programLimitName = PROGRAM_NAME_LIMIT let limitedProgramName = if trace.program.len > programLimitName: ".." & ($trace.program)[^programLimitName..^1] else: @@ -255,7 +257,7 @@ proc onlineFormView(self: WelcomeScreenComponent): VNode = # cast[JsObject](self.newRecord.workDir) self.data.ipc.send( "CODETRACER::download-trace-file", js{ - downloadId: concat(self.newDownload.args), + downloadKey: concat(self.newDownload.args), } ) ): From ca42b21705ec6b5cfd9a4e9631dc35ba5de8072e Mon Sep 17 00:00:00 2001 From: pxor Date: Fri, 7 Mar 2025 14:10:47 +0200 Subject: [PATCH 12/17] cleanup: Make a function for the welcomeScreen to reset welcome screen view --- src/frontend/ui/editor.nim | 5 +---- src/frontend/utils.nim | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/frontend/ui/editor.nim b/src/frontend/ui/editor.nim index 4653674dc..8c2198eca 100644 --- a/src/frontend/ui/editor.nim +++ b/src/frontend/ui/editor.nim @@ -311,10 +311,7 @@ proc styleLines(self: EditorViewComponent, editor: MonacoEditor, lines: seq[Mona self.decorations.mapIt(it[0])) if not self.data.ui.welcomeScreen.isNil: - self.data.ui.welcomeScreen.loading = false - self.data.ui.welcomeScreen.welcomeScreen = false - self.data.ui.welcomeScreen.newRecordScreen = false - self.data.ui.welcomeScreen.openOnlineTrace = false + self.data.ui.welcomeScreen.resetView() proc lineActionClick(self: EditorViewComponent, tabInfo: TabInfo, line: js) = var element = line diff --git a/src/frontend/utils.nim b/src/frontend/utils.nim index 716822083..31b0e65a1 100644 --- a/src/frontend/utils.nim +++ b/src/frontend/utils.nim @@ -1312,3 +1312,9 @@ proc clearViewZones*(self: EditorViewComponent) = self.monacoEditor.changeViewZones do (view: js): for viewZone in self.viewZones: view.removeZone(viewZone) + +proc resetView*(self: WelcomeScreenComponent) = + self.loading = false + self.welcomeScreen = false + self.newRecordScreen = false + self.openOnlineTrace = false \ No newline at end of file From 3757983a4ff425c286107a3454ae4e71c9c00aff Mon Sep 17 00:00:00 2001 From: pxor Date: Mon, 10 Mar 2025 14:26:08 +0200 Subject: [PATCH 13/17] feat: Add delete functionality and dynamic set of share options depending on the trace fields Add fields for the downloadKey, controlId and expireTime to the trace object Add a Command for delete of trace files Handle deleting of the trace file's download key, controlId and expireTime --- src/common/common_trace_index.nim | 2 +- src/common/common_types.nim | 7 ++++ src/common/trace_index.nim | 33 ++++++++++++++++--- src/ct/codetracerconf.nim | 12 ++++++- src/frontend/index.nim | 13 +++++++- .../styles/components/welcome_screen.styl | 6 ++++ src/frontend/ui/welcome_screen.nim | 31 +++++++++++++---- 7 files changed, 89 insertions(+), 15 deletions(-) diff --git a/src/common/common_trace_index.nim b/src/common/common_trace_index.nim index 58004444e..c34a253fc 100644 --- a/src/common/common_trace_index.nim +++ b/src/common/common_trace_index.nim @@ -57,6 +57,6 @@ const SQL_ALTER_TABLE_STATEMENTS: seq[string] = @[ # example: adding a new column """ALTER TABLE traces ADD COLUMN remoteShareDownloadId text;""", """ALTER TABLE traces ADD COLUMN remoteShareControlId text;""", - """ALTER TABLE traces ADD COLUMN remoteShareExpireTime text;""" + """ALTER TABLE traces ADD COLUMN remoteShareExpireTime INTEGER DEFAULT -1;""" # """ALTER TABLE traces ADD COLUMN love integer;""" ] diff --git a/src/common/common_types.nim b/src/common/common_types.nim index b50492007..e2e9fd30b 100644 --- a/src/common/common_types.nim +++ b/src/common/common_types.nim @@ -153,6 +153,9 @@ type rrPid*: int exitCode*: int calltraceMode*: CalltraceMode + downloadKey*: langstring + controlId*: langstring + onlineExpireTime*: int CalltraceMode* {.pure.} = enum NoInstrumentation, CallKeyOnly, RawRecordNoValues, FullRecord @@ -1346,6 +1349,10 @@ type trace*: Trace programName*: langstring + DeleteTraceArg* = object + traceId*: int + controlId*: langstring + DbEventKind* {.pure.} = enum Record, Trace, History RegisterEventsArg* = object diff --git a/src/common/trace_index.nim b/src/common/trace_index.nim index 582a327ef..c3e54329a 100644 --- a/src/common/trace_index.nim +++ b/src/common/trace_index.nim @@ -77,6 +77,19 @@ proc updateField*( ) db.close() +proc updateField*( + id: int, + fieldName: string, + fieldValue: int, + test: bool +) = + let db = ensureDB(test) + db.exec( + sql(&"UPDATE traces SET {fieldName} = ? WHERE id = ?"), + fieldValue, id + ) + db.close() + proc getField*( id: int, fieldName: string, @@ -109,7 +122,8 @@ proc recordTrace*( exitCode: int, calltrace: bool, calltraceMode: CalltraceMode, - test: bool): Trace = + test: bool, + downloadKey: string = ""): Trace = # TODO pass here a Trace value and instead if neeeded construct it from other helpers let currentDate: DateTime = now() @@ -135,19 +149,19 @@ proc recordTrace*( sourceFolders, lowLevelFolder, outputFolder, lang, imported, shellID, rrPid, exitCode, - calltrace, calltraceMode, date) + calltrace, calltraceMode, date, remoteShareDownloadId) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?)""", + ?, ?, ?, ?)""", $id, program, args.join(" "), compileCommand, env, workdir, "", # <- output sourceFolders, lowLevelFolder, outputFolder, $(lang.int), $(imported.int), $shellID, $rrPid, $exitCode, - ord(calltrace), $calltraceMode, $traceDate) + ord(calltrace), $calltraceMode, $traceDate, downloadKey) break except DbError: echo "error: ", getCurrentExceptionMsg() @@ -205,6 +219,12 @@ proc loadCalltraceMode*(raw: string, lang: Lang): CalltraceMode = proc loadTrace(trace: Row, test: bool): Trace = try: let lang = trace[10].parseInt.Lang + var expireTime = -1 + try: + expireTime = trace[20].parseInt + except: + discard + result = Trace( id: trace[0].parseInt, program: trace[1], @@ -224,7 +244,10 @@ proc loadTrace(trace: Row, test: bool): Trace = shellID: trace[14].parseInt, calltrace: trace[15].parseInt != 0, - calltraceMode: loadCalltraceMode(trace[16], lang)) + calltraceMode: loadCalltraceMode(trace[16], lang), + downloadKey: trace[18], + controlId: trace[19], + onlineExpireTime: expireTime) except CatchableError as e: # assume db schema change? echo "internal error: ", e.msg diff --git a/src/ct/codetracerconf.nim b/src/ct/codetracerconf.nim index 7fcfa0ff1..7d302c21f 100644 --- a/src/ct/codetracerconf.nim +++ b/src/ct/codetracerconf.nim @@ -14,6 +14,7 @@ type install, upload, download, + cmdDelete, # build, record, console, @@ -264,7 +265,16 @@ type of download: traceRegistryId* {. argument, - desc: "the trace registry unique id: # e.g. a.rb#5" + desc: "the trace registry unique id: //// e.g. noir//1234//asd" + .}: string + of cmdDelete: + traceId* {. + name: "trace-id" + desc: "trace trace unique id" + .}: int + controlId* {. + name: "control-id", + desc: "the trace control id to delete the online trace" .}: string of start_core: coreTraceArg* {. diff --git a/src/frontend/index.nim b/src/frontend/index.nim index 8a4faaf11..3170c5822 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -811,7 +811,7 @@ proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = ] ) -proc onDownloadTraceFile(sender: js, response: jsobject(downloadKey=seq[cstring])) {.async.} = +proc onDownloadTraceFile(sender: js, response: jsobject(downloadKey = seq[cstring])) {.async.} = let res = await readProcessOutput( codetracerExe.cstring, @[j"download"].concat(response.downloadKey) @@ -822,6 +822,16 @@ proc onDownloadTraceFile(sender: js, response: jsobject(downloadKey=seq[cstring] await prepareForLoadingTrace(traceId, nodeProcess.pid.to(int)) await loadExistingRecord(traceId) +proc onDeleteOnlineTraceFile(sender: js, response: DeleteTraceArg) {.async.} = + let res = await readProcessOutput( + codetracerExe.cstring, + @[ + j"cmdDelete", + j"--trace-id=" & $response.traceId, + j"--control-id=" & response.controlId + ] + ) + proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( codetracerExe.cstring, @@ -1323,6 +1333,7 @@ proc configureIpcMain = # Upload/Download "upload-trace-file" "download-trace-file" + "delete-online-trace-file" "restart" diff --git a/src/frontend/styles/components/welcome_screen.styl b/src/frontend/styles/components/welcome_screen.styl index bfdacf88d..bb9b7df53 100644 --- a/src/frontend/styles/components/welcome_screen.styl +++ b/src/frontend/styles/components/welcome_screen.styl @@ -440,3 +440,9 @@ height: 30px font-family: "FiraCode" font-size: 20px + +.online-functionality-buttons + display: flex + + #delete-button + padding-left: 4px \ No newline at end of file diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index eb304cf77..49f58aa57 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -12,6 +12,13 @@ proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) = programName: trace.program ) +proc deleteUploadedTrace(self: WelcomeScreenComponent, trace: Trace) = + self.data.ipc.send "CODETRACER::delete-online-trace-file", + DeleteTraceArg( + traceId: trace.id, + controlId: trace.controlId + ) + proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = buildHtml( tdiv( @@ -35,13 +42,23 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = separateBar() span(class = "recent-trace-title-content"): text limitedProgramName # TODO: tippy - tdiv(class = "recent-trace-buttons"): - span( - onclick = proc(ev: Event, tg: VNode) = - ev.stopPropagation() - self.uploadTrace(trace) - ): - text "^" + tdiv(class = "online-functionality-buttons"): + if trace.downloadKey == "" and trace.onlineExpireTime == -1: + tdiv(class = "recent-trace-buttons", id = "upload-button"): + span( + onclick = proc(ev: Event, tg: VNode) = + ev.stopPropagation() + self.uploadTrace(trace) + ): + text "upload" + if trace.controlId != "": + tdiv(class = "recent-trace-buttons", id = "delete-button"): + span( + onclick = proc(ev: Event, tg: VNode) = + ev.stopPropagation() + self.deleteUploadedTrace(trace) + ): + text "delete" # tdiv(class = "recent-trace-info"): # tdiv(class = "recent-trace-date"): # text trace.date From 51c5f1468431782173bdf207962f93d9631b7b93 Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 11 Mar 2025 12:31:15 +0200 Subject: [PATCH 14/17] feat: Send a frontend update when an upload or delete of trace has finished and handle the frontend update of the trace object --- src/common/common_types.nim | 5 ++++ src/frontend/index.nim | 35 +++++++++++++++++++++++++++ src/frontend/renderer.nim | 6 +++++ src/frontend/ui/welcome_screen.nim | 38 +++++++++++++++++++++++------- src/frontend/ui_js.nim | 3 +++ 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/common/common_types.nim b/src/common/common_types.nim index e2e9fd30b..d8d2f71e9 100644 --- a/src/common/common_types.nim +++ b/src/common/common_types.nim @@ -1349,6 +1349,11 @@ type trace*: Trace programName*: langstring + UploadedTraceData* = object + downloadKey*: langstring + controlId*: langstring + expireTime*: langstring + DeleteTraceArg* = object traceId*: int controlId*: langstring diff --git a/src/frontend/index.nim b/src/frontend/index.nim index 3170c5822..1d0ab10c8 100644 --- a/src/frontend/index.nim +++ b/src/frontend/index.nim @@ -811,6 +811,33 @@ proc onUploadTraceFile(sender: js, response: UploadTraceArg) {.async.} = ] ) + if res.isOk: + let splitData = res.v.split("\n") + if splitData.len() == 4: + let uploadData = UploadedTraceData( + downloadKey: splitData[0], + controlId: splitData[1], + expireTime: splitData[2] + ) + mainWindow.webContents.send( + "CODETRACER::uploaded-trace-received", + js{ + "argId": j(response.trace.program & ":" & $response.trace.id), + "value": uploadData + } + ) + else: + let uploadData = UploadedTraceData( + downloadKey: splitData[0], + ) + mainWindow.webContents.send( + "CODETRACER::uploaded-trace-received", + js{ + "argId": j(response.trace.program & ":" & $response.trace.id), + "value": uploadData + } + ) + proc onDownloadTraceFile(sender: js, response: jsobject(downloadKey = seq[cstring])) {.async.} = let res = await readProcessOutput( codetracerExe.cstring, @@ -832,6 +859,14 @@ proc onDeleteOnlineTraceFile(sender: js, response: DeleteTraceArg) {.async.} = ] ) + mainWindow.webContents.send( + "CODETRACER::deleted-online-trace-received", + js{ + "argId": j($response.traceId & ":" & response.controlId), + "value": res.isOk + } + ) + proc onSendBugReportAndLogs(sender: js, response: BugReportArg) {.async.} = let process = await runProcess( codetracerExe.cstring, diff --git a/src/frontend/renderer.nim b/src/frontend/renderer.nim index 95f831636..f2220e619 100644 --- a/src/frontend/renderer.nim +++ b/src/frontend/renderer.nim @@ -488,6 +488,12 @@ proc onContextStartHistory*(sender: js, response: jsobject(inState=bool, express proc onLoadParsedExprsReceived*(sender: js, response: jsobject(argId=cstring, value=JsAssoc[cstring, seq[FlowExpression]])) = jsAsFunction[proc(response: JsAssoc[cstring, seq[FlowExpression]]): void](data.network.futures["load-parsed-exprs"][response.argId])(response.value) +proc onUploadedTraceReceived*(sender: js, response: jsobject(argId=cstring, value=UploadedTraceData)) = + jsAsFunction[proc(response: UploadedTraceData): void](data.network.futures["upload-trace-file"][response.argId])(response.value) + +proc onDeletedOnlineTraceReceived*(sender: js, response: jsobject(argId=cstring, value=bool)) = + jsAsFunction[proc(response: bool): void](data.network.futures["delete-online-trace-file"][response.argId])(response.value) + # TODO: make some kind of dsl? # locals proc onLoadLocalsReceived*(sender: js, response: jsobject(argId=cstring, value=JsAssoc[cstring, Value])) = diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index 49f58aa57..d9597e1ca 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -5,19 +5,41 @@ import const PROGRAM_NAME_LIMIT = 45 -proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) = - self.data.ipc.send "CODETRACER::upload-trace-file", +proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) {.async.} = + var uploadedData = await self.data.asyncSend( + "upload-trace-file", UploadTraceArg( trace: trace, programName: trace.program - ) + ), + &"{trace.program}:{trace.id}", UploadedTraceData + ) + + if uploadedData.downloadKey != "Errored": + trace.downloadKey = uploadedData.downloadKey + trace.controlId = uploadedData.controlId + trace.onlineExpireTime = ($uploadedData.expireTime).parseInt() + else: + trace.downloadKey = uploadedData.downloadKey + + self.data.redraw() -proc deleteUploadedTrace(self: WelcomeScreenComponent, trace: Trace) = - self.data.ipc.send "CODETRACER::delete-online-trace-file", +proc deleteUploadedTrace(self: WelcomeScreenComponent, trace: Trace) {.async.} = + var deleted = await self.data.asyncSend( + "delete-online-trace-file", DeleteTraceArg( traceId: trace.id, controlId: trace.controlId - ) + ), + &"{trace.id}:{trace.controlId}", bool + ) + + if deleted: + trace.controlId = "" + trace.downloadKey = "" + trace.onlineExpireTime = -1 + + self.data.redraw() proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = buildHtml( @@ -48,7 +70,7 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = span( onclick = proc(ev: Event, tg: VNode) = ev.stopPropagation() - self.uploadTrace(trace) + discard self.uploadTrace(trace) ): text "upload" if trace.controlId != "": @@ -56,7 +78,7 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = span( onclick = proc(ev: Event, tg: VNode) = ev.stopPropagation() - self.deleteUploadedTrace(trace) + discard self.deleteUploadedTrace(trace) ): text "delete" # tdiv(class = "recent-trace-info"): diff --git a/src/frontend/ui_js.nim b/src/frontend/ui_js.nim index 62c3b209d..41e960c21 100644 --- a/src/frontend/ui_js.nim +++ b/src/frontend/ui_js.nim @@ -1090,6 +1090,9 @@ proc configureIPC(data: Data) = "follow-history" + "uploaded-trace-received" + "deleted-online-trace-received" + duration("configureIPCRun") proc zoomInEditors*(data: Data) = From b8a295a60ca48b9c33f441fc4d0bcf18e1dc01de Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 11 Mar 2025 13:46:33 +0200 Subject: [PATCH 15/17] fix: Rebase issues --- src/ct/launch/launch.nim | 17 ++-- src/ct/online_sharing/security_upload.nim | 68 +++++++++++++++ src/ct/online_sharing/trace_manager.nim | 100 ++++++++++++++++++++++ src/ct/trace/record.nim | 2 +- src/ct/trace/replay.nim | 2 +- src/ct/trace/storage_and_import.nim | 41 +++++++-- 6 files changed, 213 insertions(+), 17 deletions(-) create mode 100644 src/ct/online_sharing/security_upload.nim create mode 100644 src/ct/online_sharing/trace_manager.nim diff --git a/src/ct/launch/launch.nim b/src/ct/launch/launch.nim index 18e6f8827..8a5b7910e 100644 --- a/src/ct/launch/launch.nim +++ b/src/ct/launch/launch.nim @@ -2,6 +2,7 @@ import std/[strutils, strformat, osproc], ../../common/[ paths, types, intel_fix, install_utils, trace_index, start_utils ], ../utilities/[ git, env ], + ../online_sharing/trace_manager, ../cli/[ logging, list, help ], ../trace/[ replay, record, run, metadata ], ../codetracerconf, @@ -101,15 +102,15 @@ proc runInitial*(conf: CodetracerConf) = notSupportedCommand($conf.cmd) of StartupCommand.upload: # similar to replay/console - notSupportedCommand($conf.cmd) - # eventually enable? - # uploadCommand( - # conf.uploadLastTraceMatchingPattern, - # conf.uploadTraceId, - # conf.uploadTraceFolder, - # replayInteractive) + uploadCommand( + conf.uploadLastTraceMatchingPattern, + conf.uploadTraceId, + conf.uploadTraceFolder, + replayInteractive) of StartupCommand.download: - notSupportedCommand($conf.cmd) + downloadCommand(conf.traceRegistryId) + of StartupCommand.cmdDelete: + deleteTraceCommand(conf.traceId, conf.controlId) # eventually enable? # downloadCommand(conf.traceRegistryId) # of StartupCommand.build: diff --git a/src/ct/online_sharing/security_upload.nim b/src/ct/online_sharing/security_upload.nim new file mode 100644 index 000000000..c2a34c092 --- /dev/null +++ b/src/ct/online_sharing/security_upload.nim @@ -0,0 +1,68 @@ +import nimcrypto, zip/zipfiles, std/[ sequtils, strutils, strformat, os, osproc ] +import ../../common/[ config, trace_index, lang ] + +proc generateSecurePassword*(): string = + var key: array[32, byte] + discard randomBytes(key) + + result = key.mapIt(it.toHex(2)).join("") + return result + +proc pkcs7Pad*(data: seq[byte], blockSize: int): seq[byte] = + let padLen = blockSize - (data.len mod blockSize) + result = data & repeat(cast[byte](padLen), padLen) + +proc pkcs7Unpad*(data: seq[byte]): seq[byte] = + if data.len == 0: + raise newException(ValueError, "Data is empty, cannot unpad") + + let padLen = int64(data[^1]) # Convert last byte to int64 safely + if padLen <= 0 or padLen > data.len: + raise newException(ValueError, "Invalid padding") + + result = data[0 ..< data.len - padLen] + +func toBytes*(s: string): seq[byte] = + ## Convert a string to the corresponding byte sequence - since strings in + ## nim essentially are byte sequences without any particular encoding, this + ## simply copies the bytes without a null terminator + when nimvm: + var r = newSeq[byte](s.len) + for i, c in s: + r[i] = cast[byte](c) + r + else: + @(s.toOpenArrayByte(0, s.high)) + +proc encryptZip(zipFile, password: string) = + var iv: seq[byte] = password.toBytes()[0..15] + + var aes: CBC[aes256] + aes.init(password.toOpenArrayByte(0, len(password) - 1), iv) + + var zipData = readFile(zipFile).toBytes() + var paddedData = pkcs7Pad(zipData, 16) + var encrypted = newSeq[byte](paddedData.len) + + aes.encrypt(paddedData, encrypted.toOpenArray(0, len(encrypted) - 1)) + writeFile(zipFile & ".enc", encrypted) + +proc zipFileWithEncryption*(inputFile: string, outputZip: string, password: string) = + var zip: ZipArchive + if not zip.open(outputZip, fmWrite): + raise newException(IOError, "Failed to create zip file: " & outputZip) + + for file in walkDirRec(inputFile): + let relPath = file.relativePath(inputFile) + zip.addFile(relPath, file) + + zip.close() + encryptZip(outputZip, password) + removeFile(outputZip) + +proc uploadEncyptedZip*(file: string): (string, int) = + # TODO: Plug in http client instead of curl + let config = loadConfig(folder=getCurrentDir(), inTest=false) + let cmd = &"curl -s -X POST -F \"file=@{file}.enc\" {config.webApiRoot}/upload" + let (output, exitCode) = execCmdEx(cmd) + (output, exitCode) \ No newline at end of file diff --git a/src/ct/online_sharing/trace_manager.nim b/src/ct/online_sharing/trace_manager.nim new file mode 100644 index 000000000..60101652b --- /dev/null +++ b/src/ct/online_sharing/trace_manager.nim @@ -0,0 +1,100 @@ +import std/[ options, strutils, os, osproc, strformat, json ], ../trace/replay, ../codetracerconf, zip/zipfiles, nimcrypto +import ../../common/[ config, trace_index, lang ] +import ../utilities/language_detection +import ../trace/[ storage_and_import, record ] +import security_upload + +proc uploadCommand*( + patternArg: Option[string], + traceIdArg: Option[int], + traceFolderArg: Option[string], + interactive: bool +) = + discard internalReplayOrUpload(patternArg, traceIdArg, traceFolderArg, interactive, command=StartupCommand.upload) + + +proc decryptZip(encryptedFile: string, password: string, outputFile: string) = + var encData = readFile(encryptedFile).toBytes() + if encData.len < 16: + raise newException(ValueError, "Invalid encrypted data (too short)") + + let iv = password.toBytes()[0 ..< 16] + let ciphertext = encData[16 .. ^1] + let key = password.toBytes() + + var aes: CBC[aes256] + aes.init(key, iv) + + var decrypted = newSeq[byte](encData.len) + aes.decrypt(encData, decrypted.toOpenArray(0, len(decrypted) - 1)) + + var depaddedData = pkcs7Unpad(decrypted) + writeFile(outputFile, depaddedData) + +proc unzipDecryptedFile(zipFile: string, outputDir: string): (string, int) = + var zip: ZipArchive + if not zip.open(zipFile, fmRead): + raise newException(IOError, "Failed to open decrypted ZIP: " & zipFile) + + let traceId = trace_index.newID(false) + let outPath = outputDir / "trace-" & $traceId + + createDir(outPath) + zip.extractAll(outPath) + + zip.close() + return (outPath, traceId) + +proc downloadCommand*(traceRegistryId: string) = + # We expect a traceRegistryId to have :: + let stringSplit = traceRegistryId.split("//") + if stringSplit.len() != 3: + quit(1) + else: + let downloadId = stringSplit[1] + let password = stringSplit[2] + let zipPath = "/tmp/tmp.zip" + let config = loadConfig(folder=getCurrentDir(), inTest=false) + let localPath = "/tmp" / "tmp.zip.enc" + # TODO: Plug in an http client + let cmd = &"curl -s -o {localPath} {config.webApiRoot}/download?DownloadId={downloadId}" + let (output, exitCode) = execCmdEx(cmd) + + decryptZip(localPath, password, zipPath) + + let (traceFolder, traceId) = unzipDecryptedFile(zipPath, os.getHomeDir() / ".local" / "share" / "codetracer") + let tracePath = traceFolder / "trace.json" + let traceJson = parseJson(readFile(tracePath)) + let traceMetadataPath = traceFolder / "trace_metadata.json" + + var pathValue = "" + + for item in traceJson: + if item.hasKey("Path"): + pathValue = item["Path"].getStr("") + break + + let lang = detectLang(pathValue, LangUnknown) + discard importDbTrace(traceMetadataPath, traceId, lang, DB_SELF_CONTAINED_DEFAULT, traceRegistryId) + + removeFile(localPath) + removeFile(zipPath) + + echo traceId + + quit(exitCode) + +proc deleteAndResetFields(id: int, test: bool) = + updateField(id, "remoteShareDownloadId", "", test) + updateField(id, "remoteShareControlId", "", test) + updateField(id, "remoteShareExpireTime", -1, test) + +proc deleteTraceCommand*(id: int, controlId: string) = + let config = loadConfig(folder=getCurrentDir(), inTest=false) + let cmd = &"curl -s {config.webApiRoot}/delete?ControlId={controlId}" + let (output, exitCode) = execCmdEx(cmd) + + if exitCode == 0: + deleteAndResetFields(id, false) + + quit(exitCode) \ No newline at end of file diff --git a/src/ct/trace/record.nim b/src/ct/trace/record.nim index 49c90a40a..3df81bd92 100644 --- a/src/ct/trace/record.nim +++ b/src/ct/trace/record.nim @@ -54,7 +54,7 @@ proc recordSymbols(sourceDir: string, outputFolder: string, lang: Lang) = # it's still good to have an option/opt-out, so we leave that # as a flag in the internals, but not exposed to user yet # that's why for now it's hardcoded for db -const DB_SELF_CONTAINED_DEFAULT = true +const DB_SELF_CONTAINED_DEFAULT* = true # rr patches for ruby/other vm-s: not supported now, instead # in db backend support only direct traces diff --git a/src/ct/trace/replay.nim b/src/ct/trace/replay.nim index 1a23a76c9..1474616a1 100644 --- a/src/ct/trace/replay.nim +++ b/src/ct/trace/replay.nim @@ -6,7 +6,7 @@ import std/[options ], shell, run -proc internalReplayOrUpload( +proc internalReplayOrUpload*( patternArg: Option[string], traceIdArg: Option[int], traceFolderArg: Option[string], diff --git a/src/ct/trace/storage_and_import.nim b/src/ct/trace/storage_and_import.nim index 9c0164ca9..428089194 100644 --- a/src/ct/trace/storage_and_import.nim +++ b/src/ct/trace/storage_and_import.nim @@ -2,6 +2,7 @@ import std/[os, json, strutils, strformat, sets, algorithm], ../../common/[trace_index, lang, types, paths], ../utilities/git, + ../online_sharing/security_upload, json_serialization proc storeTraceFiles(paths: seq[string], traceFolder: string, lang: Lang) = @@ -78,10 +79,12 @@ proc processSourceFoldersList*(folderSet: HashSet[string], programDir: string = proc importDbTrace*( - traceMetadataPath: string, - traceIdArg: int, - lang: Lang = LangNoir, - selfContained: bool = true): Trace = + traceMetadataPath: string, + traceIdArg: int, + lang: Lang = LangNoir, + selfContained: bool = true, + downloadKey: string = "" +): Trace = let rawTraceMetadata = readFile(traceMetadataPath) let untypedJson = parseJson(rawTraceMetadata) let program = untypedJson{"program"}.getStr() @@ -155,8 +158,32 @@ proc importDbTrace*( calltrace = true, # for now always use FullRecord for db-backend # and ignore possible env var override - calltraceMode = CalltraceMode.FullRecord) + calltraceMode = CalltraceMode.FullRecord, + downloadKey = downloadKey) proc uploadTrace*(trace: Trace) = - echo "error: uploading traces not supported currently!" - quit(1) + let outputZip = trace.outputFolder / "tmp.zip" + let aesKey = generateSecurePassword() + + zipFileWithEncryption(trace.outputFolder, outputZip, aesKey) + + let (output, exitCode) = uploadEncyptedZip(outputZip) + let jsonMessage = parseJson(output) + let downloadKey = trace.program & "//" & jsonMessage["DownloadId"].getStr("") & "//" & aesKey + + if jsonMessage["DownloadId"].getStr("") notin @["", "Errored"]: + + updateField(trace.id, "remoteShareDownloadId", downloadKey, false) + updateField(trace.id, "remoteShareControlId", jsonMessage["ControlId"].getStr(""), false) + updateField(trace.id, "remoteShareExpireTime", jsonMessage["Expires"].getInt(), false) + + echo downloadKey + echo jsonMessage["ControlId"].getStr("") + echo jsonMessage["Expires"].getInt() + + else: + echo downloadKey + + removeFile(outputZip & ".enc") + + quit(exitCode) From e646dbc352e45259a129038c05d17bb57154fd1b Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 11 Mar 2025 14:11:13 +0200 Subject: [PATCH 16/17] feat: Add expire time and copy download key button --- .../styles/components/welcome_screen.styl | 8 ++++- src/frontend/ui/welcome_screen.nim | 29 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/frontend/styles/components/welcome_screen.styl b/src/frontend/styles/components/welcome_screen.styl index bb9b7df53..2e177cf95 100644 --- a/src/frontend/styles/components/welcome_screen.styl +++ b/src/frontend/styles/components/welcome_screen.styl @@ -445,4 +445,10 @@ display: flex #delete-button - padding-left: 4px \ No newline at end of file + padding-left: 4px + + #copy-button + padding-left: 4px + + .expire-time-text + white-space: nowrap \ No newline at end of file diff --git a/src/frontend/ui/welcome_screen.nim b/src/frontend/ui/welcome_screen.nim index d9597e1ca..ffcb65331 100644 --- a/src/frontend/ui/welcome_screen.nim +++ b/src/frontend/ui/welcome_screen.nim @@ -2,8 +2,11 @@ import ../ui_helpers, ../../ct/version, ui_imports, ../types +import std/times except now const PROGRAM_NAME_LIMIT = 45 +const NO_EXPIRE_TIME = -1 +const EMPTY_STRING = "" proc uploadTrace(self: WelcomeScreenComponent, trace: Trace) {.async.} = var uploadedData = await self.data.asyncSend( @@ -35,9 +38,9 @@ proc deleteUploadedTrace(self: WelcomeScreenComponent, trace: Trace) {.async.} = ) if deleted: - trace.controlId = "" - trace.downloadKey = "" - trace.onlineExpireTime = -1 + trace.controlId = EMPTY_STRING + trace.downloadKey = EMPTY_STRING + trace.onlineExpireTime = NO_EXPIRE_TIME self.data.redraw() @@ -65,7 +68,12 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = span(class = "recent-trace-title-content"): text limitedProgramName # TODO: tippy tdiv(class = "online-functionality-buttons"): - if trace.downloadKey == "" and trace.onlineExpireTime == -1: + if trace.onlineExpireTime != NO_EXPIRE_TIME: + let dt = fromUnix(trace.onlineExpireTime) + let time = dt.format("dd MM yyyy") + span(class = "expire-time-text"): + text &"Expires on {time}" + if trace.downloadKey == "" and trace.onlineExpireTime == NO_EXPIRE_TIME: tdiv(class = "recent-trace-buttons", id = "upload-button"): span( onclick = proc(ev: Event, tg: VNode) = @@ -73,14 +81,25 @@ proc recentProjectView(self: WelcomeScreenComponent, trace: Trace): VNode = discard self.uploadTrace(trace) ): text "upload" - if trace.controlId != "": + if trace.controlId != EMPTY_STRING: tdiv(class = "recent-trace-buttons", id = "delete-button"): span( onclick = proc(ev: Event, tg: VNode) = ev.stopPropagation() discard self.deleteUploadedTrace(trace) ): + text "delete" + if trace.downloadKey != EMPTY_STRING: + tdiv(class = "recent-trace-buttons", id = "copy-button"): + span( + onclick = proc(ev: Event, tg: VNode) = + ev.stopPropagation() + clipboardCopy(trace.downloadKey) + ): + + text "copy" + # tdiv(class = "recent-trace-info"): # tdiv(class = "recent-trace-date"): # text trace.date From a366fd870ec9bda600143d89fb5561b9dafa5b52 Mon Sep 17 00:00:00 2001 From: pxor Date: Tue, 11 Mar 2025 15:03:36 +0200 Subject: [PATCH 17/17] fix: Minor changes from review --- src/ct/online_sharing/security_upload.nim | 4 ++-- src/ct/online_sharing/trace_manager.nim | 10 +++++----- src/frontend/styles/components/welcome_screen.styl | 2 +- src/frontend/utils.nim | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ct/online_sharing/security_upload.nim b/src/ct/online_sharing/security_upload.nim index c2a34c092..01218c6be 100644 --- a/src/ct/online_sharing/security_upload.nim +++ b/src/ct/online_sharing/security_upload.nim @@ -1,5 +1,5 @@ import nimcrypto, zip/zipfiles, std/[ sequtils, strutils, strformat, os, osproc ] -import ../../common/[ config, trace_index, lang ] +import ../../common/[ config ] proc generateSecurePassword*(): string = var key: array[32, byte] @@ -65,4 +65,4 @@ proc uploadEncyptedZip*(file: string): (string, int) = let config = loadConfig(folder=getCurrentDir(), inTest=false) let cmd = &"curl -s -X POST -F \"file=@{file}.enc\" {config.webApiRoot}/upload" let (output, exitCode) = execCmdEx(cmd) - (output, exitCode) \ No newline at end of file + (output, exitCode) diff --git a/src/ct/online_sharing/trace_manager.nim b/src/ct/online_sharing/trace_manager.nim index 60101652b..802422d07 100644 --- a/src/ct/online_sharing/trace_manager.nim +++ b/src/ct/online_sharing/trace_manager.nim @@ -1,5 +1,5 @@ import std/[ options, strutils, os, osproc, strformat, json ], ../trace/replay, ../codetracerconf, zip/zipfiles, nimcrypto -import ../../common/[ config, trace_index, lang ] +import ../../common/[ config, trace_index, lang, paths ] import ../utilities/language_detection import ../trace/[ storage_and_import, record ] import security_upload @@ -53,16 +53,16 @@ proc downloadCommand*(traceRegistryId: string) = else: let downloadId = stringSplit[1] let password = stringSplit[2] - let zipPath = "/tmp/tmp.zip" + let zipPath = codetracerTmpPath / "tmp.zip" let config = loadConfig(folder=getCurrentDir(), inTest=false) - let localPath = "/tmp" / "tmp.zip.enc" + let localPath = codetracerTmpPath / "tmp.zip.enc" # TODO: Plug in an http client let cmd = &"curl -s -o {localPath} {config.webApiRoot}/download?DownloadId={downloadId}" let (output, exitCode) = execCmdEx(cmd) decryptZip(localPath, password, zipPath) - let (traceFolder, traceId) = unzipDecryptedFile(zipPath, os.getHomeDir() / ".local" / "share" / "codetracer") + let (traceFolder, traceId) = unzipDecryptedFile(zipPath, codetracerTraceDir) let tracePath = traceFolder / "trace.json" let traceJson = parseJson(readFile(tracePath)) let traceMetadataPath = traceFolder / "trace_metadata.json" @@ -97,4 +97,4 @@ proc deleteTraceCommand*(id: int, controlId: string) = if exitCode == 0: deleteAndResetFields(id, false) - quit(exitCode) \ No newline at end of file + quit(exitCode) diff --git a/src/frontend/styles/components/welcome_screen.styl b/src/frontend/styles/components/welcome_screen.styl index 2e177cf95..86475121c 100644 --- a/src/frontend/styles/components/welcome_screen.styl +++ b/src/frontend/styles/components/welcome_screen.styl @@ -451,4 +451,4 @@ padding-left: 4px .expire-time-text - white-space: nowrap \ No newline at end of file + white-space: nowrap diff --git a/src/frontend/utils.nim b/src/frontend/utils.nim index 31b0e65a1..fd5c18486 100644 --- a/src/frontend/utils.nim +++ b/src/frontend/utils.nim @@ -1317,4 +1317,4 @@ proc resetView*(self: WelcomeScreenComponent) = self.loading = false self.welcomeScreen = false self.newRecordScreen = false - self.openOnlineTrace = false \ No newline at end of file + self.openOnlineTrace = false