diff --git a/favourite_terminal.txt b/favourite_terminal.txt
new file mode 100644
index 0000000..215d430
--- /dev/null
+++ b/favourite_terminal.txt
@@ -0,0 +1,11 @@
+xfce4-terminal -e
+
+# The first two lines of this file contain the prefix and suffix
+# that it takes to execute a shell script in your favourite terminal.
+# examples (note that some lines contain a trailing space):
+# xfce4-terminal -e
+#
+# xterm -hold -e
+#
+# terminus -e
+#
diff --git a/phyportal b/phyportal
new file mode 100644
index 0000000..423626d
Binary files /dev/null and b/phyportal differ
diff --git a/phyportal.exe b/phyportal.exe
new file mode 100644
index 0000000..86c9f2f
Binary files /dev/null and b/phyportal.exe differ
diff --git a/phyportal.ico b/phyportal.ico
new file mode 100644
index 0000000..0341321
Binary files /dev/null and b/phyportal.ico differ
diff --git a/phyportal.lpi b/phyportal.lpi
new file mode 100644
index 0000000..69896fa
--- /dev/null
+++ b/phyportal.lpi
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phyportal.lpr b/phyportal.lpr
new file mode 100644
index 0000000..d10d657
--- /dev/null
+++ b/phyportal.lpr
@@ -0,0 +1,23 @@
+program phyportal;
+
+{$mode objfpc}{$H+}
+
+uses
+ {$IFDEF UNIX}{$IFDEF UseCThreads}
+ cthreads,
+ {$ENDIF}{$ENDIF}
+ Interfaces, // this includes the LCL widgetset
+ Forms, Unit1, Unit2
+ { you can add units after this };
+
+{$R *.res}
+
+begin
+ RequireDerivedFormResource:=True;
+ Application.Scaled:=True;
+ Application.Initialize;
+ Application.CreateForm(TForm1, Form1);
+ Application.CreateForm(TForm2, Form2);
+ Application.Run;
+end.
+
diff --git a/phyportal.lps b/phyportal.lps
new file mode 100644
index 0000000..424f729
--- /dev/null
+++ b/phyportal.lps
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/phyportal.res b/phyportal.res
new file mode 100644
index 0000000..1adb040
Binary files /dev/null and b/phyportal.res differ
diff --git a/unit1.lfm b/unit1.lfm
new file mode 100644
index 0000000..eeeeabd
--- /dev/null
+++ b/unit1.lfm
@@ -0,0 +1,959 @@
+object Form1: TForm1
+ Left = 550
+ Height = 781
+ Top = 0
+ Width = 1016
+ Caption = 'phyportal'
+ ClientHeight = 781
+ ClientWidth = 1016
+ Color = clNavy
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnCreate = FormCreate
+ OnShow = FormShow
+ Position = poDesktopCenter
+ LCLVersion = '2.2.0.4'
+ object Shape1: TShape
+ Left = 13
+ Height = 288
+ Top = 64
+ Width = 986
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label1: TLabel
+ Left = 19
+ Height = 20
+ Top = 90
+ Width = 39
+ Caption = 'phy-1'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU1: TLabel
+ Left = 90
+ Height = 20
+ Top = 90
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM1: TLabel
+ Left = 90
+ Height = 20
+ Top = 115
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button2: TButton
+ Left = 90
+ Height = 25
+ Top = 141
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button2Click
+ ParentFont = False
+ TabOrder = 0
+ end
+ object Memo1: TMemo
+ Left = 397
+ Height = 76
+ Top = 90
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 1
+ WordWrap = False
+ end
+ object Button3: TButton
+ Left = 826
+ Height = 25
+ Top = 90
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button3Click
+ ParentFont = False
+ TabOrder = 2
+ end
+ object Button4: TButton
+ Left = 825
+ Height = 25
+ Top = 115
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button4Click
+ ParentFont = False
+ TabOrder = 3
+ end
+ object Label4: TLabel
+ Left = 19
+ Height = 20
+ Top = 64
+ Width = 165
+ Caption = 'IOW COMPUTE SERVERS'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Button5: TButton
+ Left = 825
+ Height = 25
+ Top = 141
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button5Click
+ ParentFont = False
+ TabOrder = 4
+ end
+ object Shape2: TShape
+ Left = 13
+ Height = 51
+ Top = 6
+ Width = 986
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label5: TLabel
+ Left = 19
+ Height = 20
+ Top = 6
+ Width = 41
+ Caption = 'LINKS'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Button1: TButton
+ Left = 19
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'beginners'' intro'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button1Click
+ ParentFont = False
+ TabOrder = 5
+ end
+ object Button6: TButton
+ Left = 134
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'phywiki'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button6Click
+ ParentFont = False
+ TabOrder = 6
+ end
+ object Button7: TButton
+ Left = 250
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'webmail'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button7Click
+ ParentFont = False
+ TabOrder = 7
+ end
+ object Button8: TButton
+ Left = 365
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'chat'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button8Click
+ ParentFont = False
+ TabOrder = 8
+ end
+ object Button9: TButton
+ Left = 480
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'owncloud'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button9Click
+ ParentFont = False
+ TabOrder = 9
+ end
+ object Button10: TButton
+ Left = 595
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'public drive'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button10Click
+ ParentFont = False
+ TabOrder = 10
+ end
+ object Button11: TButton
+ Left = 710
+ Height = 25
+ Top = 26
+ Width = 114
+ Caption = 'git server'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button11Click
+ ParentFont = False
+ TabOrder = 11
+ end
+ object Label6: TLabel
+ Left = 19
+ Height = 20
+ Top = 179
+ Width = 39
+ Caption = 'phy-2'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU2: TLabel
+ Left = 90
+ Height = 20
+ Top = 179
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM2: TLabel
+ Left = 90
+ Height = 20
+ Top = 205
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button12: TButton
+ Left = 90
+ Height = 25
+ Top = 230
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button12Click
+ ParentFont = False
+ TabOrder = 12
+ end
+ object Memo2: TMemo
+ Left = 397
+ Height = 76
+ Top = 179
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 13
+ WordWrap = False
+ end
+ object Button13: TButton
+ Left = 826
+ Height = 25
+ Top = 179
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button13Click
+ ParentFont = False
+ TabOrder = 14
+ end
+ object Button14: TButton
+ Left = 825
+ Height = 25
+ Top = 205
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button14Click
+ ParentFont = False
+ TabOrder = 15
+ end
+ object Button15: TButton
+ Left = 825
+ Height = 25
+ Top = 230
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button15Click
+ ParentFont = False
+ TabOrder = 16
+ end
+ object Label9: TLabel
+ Left = 19
+ Height = 20
+ Top = 269
+ Width = 39
+ Caption = 'phy-3'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU3: TLabel
+ Left = 90
+ Height = 20
+ Top = 269
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM3: TLabel
+ Left = 90
+ Height = 20
+ Top = 294
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button16: TButton
+ Left = 90
+ Height = 25
+ Top = 320
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button16Click
+ ParentFont = False
+ TabOrder = 17
+ end
+ object Memo3: TMemo
+ Left = 397
+ Height = 76
+ Top = 269
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 18
+ WordWrap = False
+ end
+ object Button17: TButton
+ Left = 826
+ Height = 25
+ Top = 269
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button17Click
+ ParentFont = False
+ TabOrder = 19
+ end
+ object Button18: TButton
+ Left = 825
+ Height = 25
+ Top = 294
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button18Click
+ ParentFont = False
+ TabOrder = 20
+ end
+ object Button19: TButton
+ Left = 825
+ Height = 25
+ Top = 320
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button19Click
+ ParentFont = False
+ TabOrder = 21
+ end
+ object Shape3: TShape
+ Left = 13
+ Height = 109
+ Top = 358
+ Width = 986
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label12: TLabel
+ Left = 19
+ Height = 20
+ Top = 384
+ Width = 39
+ Caption = 'phy-4'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU4: TLabel
+ Left = 90
+ Height = 20
+ Top = 384
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM4: TLabel
+ Left = 90
+ Height = 20
+ Top = 410
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button20: TButton
+ Left = 90
+ Height = 25
+ Top = 435
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button20Click
+ ParentFont = False
+ TabOrder = 22
+ end
+ object Memo4: TMemo
+ Left = 397
+ Height = 76
+ Top = 384
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 23
+ WordWrap = False
+ end
+ object Button21: TButton
+ Left = 826
+ Height = 25
+ Top = 384
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button21Click
+ ParentFont = False
+ TabOrder = 24
+ end
+ object Button22: TButton
+ Left = 825
+ Height = 25
+ Top = 410
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button22Click
+ ParentFont = False
+ TabOrder = 25
+ end
+ object Button23: TButton
+ Left = 825
+ Height = 25
+ Top = 435
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button23Click
+ ParentFont = False
+ TabOrder = 26
+ end
+ object Label15: TLabel
+ Left = 19
+ Height = 20
+ Top = 358
+ Width = 118
+ Caption = 'IOW GPU SERVER'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Shape5: TShape
+ Left = 13
+ Height = 198
+ Top = 474
+ Width = 986
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label16: TLabel
+ Left = 19
+ Height = 20
+ Top = 499
+ Width = 47
+ Caption = 'phy-10'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU5: TLabel
+ Left = 90
+ Height = 20
+ Top = 499
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM5: TLabel
+ Left = 90
+ Height = 20
+ Top = 525
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button24: TButton
+ Left = 90
+ Height = 25
+ Top = 550
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button24Click
+ ParentFont = False
+ TabOrder = 27
+ end
+ object Memo5: TMemo
+ Left = 397
+ Height = 76
+ Top = 499
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 28
+ WordWrap = False
+ end
+ object Button25: TButton
+ Left = 826
+ Height = 25
+ Top = 499
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button25Click
+ ParentFont = False
+ TabOrder = 29
+ end
+ object Button26: TButton
+ Left = 825
+ Height = 25
+ Top = 525
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button26Click
+ ParentFont = False
+ TabOrder = 30
+ end
+ object Button27: TButton
+ Left = 825
+ Height = 25
+ Top = 550
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button27Click
+ ParentFont = False
+ TabOrder = 31
+ end
+ object Label19: TLabel
+ Left = 19
+ Height = 20
+ Top = 474
+ Width = 131
+ Caption = 'IOW DATA SERVERS'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Label20: TLabel
+ Left = 19
+ Height = 20
+ Top = 589
+ Width = 47
+ Caption = 'phy-11'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelCPU6: TLabel
+ Left = 90
+ Height = 20
+ Top = 589
+ Width = 116
+ Caption = 'CPUs busy (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object LabelMEM6: TLabel
+ Left = 90
+ Height = 20
+ Top = 614
+ Width = 114
+ Caption = 'RAM used (total):'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button28: TButton
+ Left = 90
+ Height = 25
+ Top = 640
+ Width = 124
+ Caption = 'more info...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button28Click
+ ParentFont = False
+ TabOrder = 32
+ end
+ object Memo6: TMemo
+ Left = 397
+ Height = 76
+ Top = 589
+ Width = 421
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Lines.Strings = (
+ 'getm (by klingbei) uses 50 cores (expected end 2023-10-10)'
+ 'cdo merge (by thomas) uses 2 cores (expected end 2023-10-10)'
+ )
+ OnChange = Memo1Change
+ ParentFont = False
+ ReadOnly = True
+ ScrollBars = ssBoth
+ TabOrder = 33
+ WordWrap = False
+ end
+ object Button29: TButton
+ Left = 826
+ Height = 25
+ Top = 589
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button29Click
+ ParentFont = False
+ TabOrder = 34
+ end
+ object Button30: TButton
+ Left = 825
+ Height = 25
+ Top = 614
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button30Click
+ ParentFont = False
+ TabOrder = 35
+ end
+ object Button31: TButton
+ Left = 825
+ Height = 25
+ Top = 640
+ Width = 166
+ Caption = 'start console via xpra'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button31Click
+ ParentFont = False
+ TabOrder = 36
+ end
+ object Shape7: TShape
+ Left = 13
+ Height = 96
+ Top = 678
+ Width = 859
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label23: TLabel
+ Left = 19
+ Height = 20
+ Top = 678
+ Width = 164
+ Caption = 'EXTERNAL RESSOURCES'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Label24: TLabel
+ Left = 21
+ Height = 20
+ Top = 698
+ Width = 205
+ Caption = 'haumea cluster (Uni Rostock)'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Button32: TButton
+ Left = 19
+ Height = 25
+ Top = 717
+ Width = 109
+ Caption = 'get account...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button32Click
+ ParentFont = False
+ TabOrder = 37
+ end
+ object Button33: TButton
+ Left = 135
+ Height = 25
+ Top = 717
+ Width = 165
+ Caption = 'start jupyter lab'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button33Click
+ ParentFont = False
+ TabOrder = 38
+ end
+ object Button34: TButton
+ Left = 134
+ Height = 25
+ Top = 742
+ Width = 166
+ Caption = 'start console'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button34Click
+ ParentFont = False
+ TabOrder = 39
+ end
+ object Label25: TLabel
+ Left = 365
+ Height = 20
+ Top = 698
+ Width = 149
+ Caption = 'NHR supercomputers'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object Button35: TButton
+ Left = 365
+ Height = 25
+ Top = 717
+ Width = 115
+ Caption = 'get account...'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button35Click
+ ParentFont = False
+ TabOrder = 40
+ end
+ object Button36: TButton
+ Left = 481
+ Height = 25
+ Top = 717
+ Width = 178
+ Caption = 'start jupyter lab (berlin)'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button36Click
+ ParentFont = False
+ TabOrder = 41
+ end
+ object Button37: TButton
+ Left = 481
+ Height = 25
+ Top = 742
+ Width = 178
+ Caption = 'start console (berlin)'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button37Click
+ ParentFont = False
+ TabOrder = 42
+ end
+ object Button38: TButton
+ Left = 666
+ Height = 25
+ Top = 717
+ Width = 192
+ Caption = 'start jupyter lab (göttingen)'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button38Click
+ ParentFont = False
+ TabOrder = 43
+ end
+ object Button39: TButton
+ Left = 666
+ Height = 25
+ Top = 742
+ Width = 192
+ Caption = 'start console (göttingen)'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button39Click
+ ParentFont = False
+ TabOrder = 44
+ end
+ object Label26: TLabel
+ Left = 398
+ Height = 20
+ Top = 69
+ Width = 101
+ Caption = 'registered jobs'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ end
+ object Button40: TButton
+ Left = 506
+ Height = 18
+ Top = 69
+ Width = 109
+ Caption = 'refresh'
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ OnClick = Button40Click
+ ParentFont = False
+ TabOrder = 45
+ end
+ object Shape8: TShape
+ Left = 880
+ Height = 96
+ Top = 678
+ Width = 119
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label27: TLabel
+ Left = 888
+ Height = 20
+ Top = 678
+ Width = 52
+ Caption = 'STATUS'
+ Font.Color = clGreen
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ Font.Style = [fsBold]
+ ParentFont = False
+ end
+ object LabelStatus: TLabel
+ Left = 888
+ Height = 67
+ Top = 701
+ Width = 100
+ AutoSize = False
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ ParentFont = False
+ WordWrap = True
+ end
+ object Timer1: TTimer
+ Interval = 120000
+ OnTimer = Timer1Timer
+ Left = 257
+ Top = 94
+ end
+end
diff --git a/unit1.pas b/unit1.pas
new file mode 100644
index 0000000..a32cf2a
--- /dev/null
+++ b/unit1.pas
@@ -0,0 +1,1114 @@
+unit Unit1;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, Process, lclintf
+{$IFDEF WINDOWS}
+ ,ShellApi
+{$ENDIF}
+ ;
+
+type
+
+ { TForm1 }
+
+ TForm1 = class(TForm)
+ Button1: TButton;
+ Button10: TButton;
+ Button11: TButton;
+ Button12: TButton;
+ Button13: TButton;
+ Button14: TButton;
+ Button15: TButton;
+ Button16: TButton;
+ Button17: TButton;
+ Button18: TButton;
+ Button19: TButton;
+ Button2: TButton;
+ Button20: TButton;
+ Button21: TButton;
+ Button22: TButton;
+ Button23: TButton;
+ Button24: TButton;
+ Button25: TButton;
+ Button26: TButton;
+ Button27: TButton;
+ Button28: TButton;
+ Button29: TButton;
+ Button3: TButton;
+ Button30: TButton;
+ Button31: TButton;
+ Button32: TButton;
+ Button33: TButton;
+ Button34: TButton;
+ Button35: TButton;
+ Button36: TButton;
+ Button37: TButton;
+ Button38: TButton;
+ Button39: TButton;
+ Button4: TButton;
+ Button40: TButton;
+ Button5: TButton;
+ Button6: TButton;
+ Button7: TButton;
+ Button8: TButton;
+ Button9: TButton;
+ Label1: TLabel;
+ LabelCPU3: TLabel;
+ LabelMEM3: TLabel;
+ Label12: TLabel;
+ LabelCPU4: TLabel;
+ LabelMEM4: TLabel;
+ Label15: TLabel;
+ Label16: TLabel;
+ LabelCPU5: TLabel;
+ LabelMEM5: TLabel;
+ Label19: TLabel;
+ LabelCPU1: TLabel;
+ Label20: TLabel;
+ LabelCPU6: TLabel;
+ LabelMEM6: TLabel;
+ Label23: TLabel;
+ Label24: TLabel;
+ Label25: TLabel;
+ Label26: TLabel;
+ LabelMEM1: TLabel;
+ Label4: TLabel;
+ Label5: TLabel;
+ Label6: TLabel;
+ LabelCPU2: TLabel;
+ LabelMEM2: TLabel;
+ Label9: TLabel;
+ LabelStatus: TLabel;
+ Memo1: TMemo;
+ Memo2: TMemo;
+ Memo3: TMemo;
+ Memo4: TMemo;
+ Memo5: TMemo;
+ Memo6: TMemo;
+ Shape1: TShape;
+ Shape2: TShape;
+ Shape3: TShape;
+ Shape5: TShape;
+ Shape7: TShape;
+ Timer1: TTimer;
+ procedure Button10Click(Sender: TObject);
+ procedure Button11Click(Sender: TObject);
+ procedure Button12Click(Sender: TObject);
+ procedure Button13Click(Sender: TObject);
+ procedure Button14Click(Sender: TObject);
+ procedure Button15Click(Sender: TObject);
+ procedure Button16Click(Sender: TObject);
+ procedure Button17Click(Sender: TObject);
+ procedure Button18Click(Sender: TObject);
+ procedure Button19Click(Sender: TObject);
+ procedure Button1Click(Sender: TObject);
+ procedure Button20Click(Sender: TObject);
+ procedure Button21Click(Sender: TObject);
+ procedure Button22Click(Sender: TObject);
+ procedure Button23Click(Sender: TObject);
+ procedure Button24Click(Sender: TObject);
+ procedure Button25Click(Sender: TObject);
+ procedure Button26Click(Sender: TObject);
+ procedure Button27Click(Sender: TObject);
+ procedure Button28Click(Sender: TObject);
+ procedure Button29Click(Sender: TObject);
+ procedure Button2Click(Sender: TObject);
+ procedure Button30Click(Sender: TObject);
+ procedure Button31Click(Sender: TObject);
+ procedure Button32Click(Sender: TObject);
+ procedure Button33Click(Sender: TObject);
+ procedure Button34Click(Sender: TObject);
+ procedure Button35Click(Sender: TObject);
+ procedure Button36Click(Sender: TObject);
+ procedure Button37Click(Sender: TObject);
+ procedure Button38Click(Sender: TObject);
+ procedure Button39Click(Sender: TObject);
+ procedure Button3Click(Sender: TObject);
+ procedure Button40Click(Sender: TObject);
+ procedure Button4Click(Sender: TObject);
+ procedure Button5Click(Sender: TObject);
+ procedure Button6Click(Sender: TObject);
+ procedure Button7Click(Sender: TObject);
+ procedure Button8Click(Sender: TObject);
+ procedure Button9Click(Sender: TObject);
+ procedure FormCreate(Sender: TObject);
+ procedure FormShow(Sender: TObject);
+ procedure Label6Click(Sender: TObject);
+ procedure Memo1Change(Sender: TObject);
+ procedure Timer1Timer(Sender: TObject);
+ private
+
+ public
+
+ end;
+
+var
+ Form1: TForm1;
+ haumea_unlocked, hlrn_unlocked: Boolean;
+ favouriteTerminalStart, favouriteTerminalEnd: String;
+
+implementation
+
+uses Unit2;
+
+{$R *.lfm}
+
+procedure OpenNmonUrl(hostname: String);
+begin
+ OpenUrl('http://phywiki.io-warnemuende.de/local_htmls/nmon/nmon_stat_'+hostname+'.html')
+end;
+
+function funIstVerzeichnisLeer(strVerzeichnis: string): boolean;
+// check if directory empty
+var
+ srDatensatz: TSearchRec;
+ intI: integer;
+begin
+ Result := False;
+
+ FindFirst(IncludeTrailingPathDelimiter(strVerzeichnis) + '*', faAnyFile, srDatensatz);
+
+ for intI := 1 to 2 do
+ if (srDatensatz.Name = '.') or (srDatensatz.Name = '..') then
+ Result := FindNext(srDatensatz) <> 0;
+
+ FindClose(srDatensatz);
+end;
+
+procedure ShowUsageInfo(var sl: TStringList; var LabelCPU: TLabel; var LabelMEM: TLabel; var Memo: TMemo);
+begin
+ LabelCPU.Caption := 'CPUs used (total): '+trim(sl[0])+' ('+trim(sl[1])+')';
+ sl.Delete(0); sl.Delete(0);
+ LabelMEM.Caption := 'RAM used (total): '+
+ IntToStr(round(StrToInt(trim(sl[0]))/1024/1024))+' GB ('+
+ IntToStr(round(StrToInt(trim(sl[1]))/1024/1024))+' GB)';
+ sl.Delete(0); sl.Delete(0);
+ Memo.Lines.Clear;
+ while (sl.count>1) and (sl[0]<>'*****') do
+ begin
+ Memo.Lines.Add(sl[0]);
+ sl.Delete(0);
+ end;
+ sl.Delete(0);
+ Memo.SelStart := 0;
+ Memo.SelLength := 0;
+end;
+
+function SpaceItem(var s: String): String;
+//returns the text before the first space and removes this part from the input string
+begin
+ s:=trim(s);
+ if pos(' ',s)>0 then
+ begin
+ result:=copy(s,1,pos(' ',s)-1);
+ s:=trim(copy(s,pos(' ',s)+1,length(s)));
+ end
+ else
+ begin
+ result:=s;
+ s:='';
+ end;
+end;
+
+function MyRunProcess(command: String; hidden: Boolean; wait: Boolean; var process: TProcess): TStringList;
+var
+ mycommand: String;
+ AProcess: TProcess;
+ AStringList: TStringList;
+ si: String;
+begin
+ AProcess := TProcess.Create(nil);
+ //split into command and arguments
+ mycommand:=command;
+ AProcess.Executable := SpaceItem(mycommand);
+ while mycommand<>'' do
+ AProcess.Parameters.Add(spaceItem(myCommand));
+ if wait then
+ AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes]
+ else
+ AProcess.Options := AProcess.Options + [poUsePipes];
+ if hidden then
+ AProcess.ShowWindow := swoHide
+ else
+ AProcess.Options := AProcess.Options + [poNewConsole];
+ AProcess.Execute;
+ AStringList := TStringList.Create;
+ if wait then AStringList.LoadFromStream(AProcess.Output);
+ process := AProcess;
+ Result := AStringList;
+end;
+
+function MyRunProcess(command: String; hidden: Boolean; wait: Boolean): TStringList; overload;
+var
+ AProcess: TProcess;
+ AStringList: TStringList;
+begin
+ AStringList:=MyRunProcess(command,hidden,wait,AProcess);
+ AProcess.Free;
+ Result := AStringList;
+end;
+
+procedure unlock_haumea;
+begin
+ MyRunProcess('xterm -e ssh-add '+GetUserDir+'/.ssh/id_rsa_unirostock',false,true);
+ haumea_unlocked:=true;
+end;
+
+procedure unlock_hlrn;
+begin
+ MyRunProcess('xterm -e ssh-add '+GetUserDir+'/.ssh/id_rsa_hlrn',false,true);
+ hlrn_unlocked:=true;
+end;
+
+procedure ShowErrorDisconnectedIOW;
+begin
+ if MessageDlg('Connection failed',
+ 'Could not connect to IOW server rdpserv. Please check if you are in the IOW Intranet.'+chr(13)+
+ 'Would you like to see instructions how to connect?',mtConfirmation,[mbYes,mbAbort],0,mbAbort)=mrYes then
+ OpenUrl('https://intranet.io-warnemuende.de/vpn-access-to-the-iow.html');
+end;
+
+procedure ShowErrorDisconnectedHaumea;
+begin
+ if MessageDlg('Connection failed',
+ 'Could not connect to haumea1. Please check if your settings in ~/.ssh/config are correct.'+chr(13)+
+ 'Would you like to see instructions how to connect?',mtConfirmation,[mbYes,mbAbort],0,mbAbort)=mrYes then
+ OpenUrl('http://phywiki.io-warnemuende.de/do/view/Server/UniRostockClusterDocu');
+end;
+
+function PortFromUserid(userid: Integer; where: String='IOW'; what: String='JUPYTER'): Integer;
+begin
+ Result:=0;
+ if (where='IOW' ) and (what='JUPYTER') then Result:=userid+10000;
+ if (where='REMOTE') and (what='JUPYTER') then Result:=userid+10000;
+ if (where='IOW' ) and (what='XPRA') then Result:=userid+20000;
+ if (where='REMOTE') and (what='XPRA') then Result:=userid+20000;
+end;
+
+procedure StartJupyterlabIOW(hostname: String);
+var
+ sl, sl1: TStringList;
+ username, command, s: String;
+ userid, port, ReadSize: Integer;
+ process: TProcess;
+ Buffer: array[0..100000] of char;
+ found, hasGPU: boolean;
+ portBlocked: Boolean;
+ F: TextFile;
+begin
+ hasGPU := (hostname='phy-4');
+ // Show Form2 to enter details for parallel processing and job registration
+ Form2.Edit1.Text:=hostname;
+ Form2.Label2.Visible:=true;
+ Form2.SpinEdit1.Visible:=true;
+ Form2.SpinEdit1.Value:=1;
+ Form2.Label3.Visible := hasGPU;
+ Form2.Edit2.Visible := hasGPU;
+ Form2.Edit2.Text := 'no GPUs';
+ Form2.Label4.Visible:=true;
+ Form2.Edit3.Visible:=true;
+ Form2.Edit3.Text:='';
+ Form2.SpinEdit2.Value:=8;
+ Form2.Label6.Visible:=false;
+ Form2.ComboBox1.Visible:=false;
+ Form2.Tag:=0;
+ Form2.ShowModal;
+ Application.ProcessMessages;
+ if Form2.Tag<>1 then exit;
+
+ Form1.LabelStatus.Caption:='connecting to IOW...';
+ Form1.LabelStatus.Repaint;
+ found:=false; // we will try to find the token in the output
+ // get IOW username and userid
+ sl:=MyRunProcess('ssh rdpserv whoami ; id -u',true,true);
+ if sl.count=2 then
+ begin
+ username:=trim(sl[0]);
+ userid:=StrToInt(sl[1]);
+ port:=PortFromUserid(userid,'IOW');
+ // write the jupyterlab script
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/jupyterlab_iow.sh');
+ rewrite(F);
+ writeln(F,'source ~/.bashrc');
+ writeln(F,'source ~/.bash_profile');
+ writeln(F,'module load miniconda3');
+ writeln(F,'ncores='+IntToStr(Form2.SpinEdit1.Value));
+ if Form2.Edit3.Text<>'' then
+ begin
+ writeln(F,'echo $BASHPID > PID');
+ if hasGPU then
+ writeln(F,'echo "$ncores CPU cores and '+Form2.Edit2.Text+'" >> PID')
+ else
+ writeln(F,'echo "$ncores CPU cores" >> PID');
+ writeln(F,'echo "runs '+IntToStr(Form2.SpinEdit2.Value)+' hours" >> PID');
+ writeln(F,'echo "'+Form2.Edit3.Text+'" >> PID');
+ writeln(F,'add_to_list < PID');
+ end;
+ writeln(F,'export OMP_NUM_THREADS=$ncores');
+ writeln(F,'export OPENBLAS_NUM_THREADS=$ncores');
+ writeln(F,'export MKL_NUM_THREADS=$ncores');
+ writeln(F,'export VECLIB_MAXIMUM_THREADS=$ncores');
+ writeln(F,'export NUMEXPR_NUM_THREADS=$ncores');
+ writeln(F,'jupyter lab');
+ closefile(F);
+ Form1.LabelStatus.Caption:='transferring job script...';
+ Form1.LabelStatus.Repaint;
+ // copy the jupyterlab script to IOW
+ command := 'scp '+ExtractFilePath(application.ExeName)+'/jupyterlab_iow.sh rdpserv:/home/nis/'+username;
+ MyRunProcess(command,true,true);
+ // convert to unix line endings and set execute permissions
+ command := 'ssh rdpserv dos2unix /home/nis/'+username+'/jupyterlab_iow.sh';
+ MyRunProcess(command,true,true);
+ command := 'ssh rdpserv chmod u+x /home/nis/'+username+'/jupyterlab_iow.sh';
+ MyRunProcess(command,true,true);
+ Form1.LabelStatus.Caption:='checking if port is free...';
+ Form1.LabelStatus.Repaint;
+ // check if port is in use
+ portBlocked:=false;
+ command := 'ssh rdpserv fuser '+IntToStr(port)+'/tcp';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>=1 then portBlocked:=true;
+ sl1.Free;
+ command := 'ssh rdpserv ssh '+hostname+' fuser '+IntToStr(port)+'/tcp';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>=1 then portBlocked:=true;
+ sl1.Free;
+ if portBlocked then
+ if MessageDlg('port in use','Port '+IntToStr(port)+' is already in use by another process. Kill that process?',mtConfirmation,[mbYes,mbAbort],0,mbAbort)=mrYes then
+ begin
+ command := 'ssh rdpserv fuser -k -n tcp '+IntToStr(port);
+ MyRunProcess(command,true,true);
+ command := 'ssh rdpserv ssh '+hostname+' fuser -k -n tcp '+IntToStr(port);
+ MyRunProcess(command,true,true);
+ sleep(1000);
+ end
+ else
+ exit;
+ Form1.LabelStatus.Caption:='running jupyterlab command...';
+ Form1.LabelStatus.Repaint;
+ // prepare the jupyterlab command and run it
+ command := 'ssh -t rdpserv -L '+IntToStr(port)+':localhost:'+IntToStr(port)+
+ ' ssh -t '+hostname+' -L '+IntToStr(port)+':localhost:'+IntToStr(port)+
+ ' /bin/bash -c "/home/nis/'+username+'/jupyterlab_iow.sh 2>&1"';
+ {$IFDEF LINUX}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,command);
+ closefile(F);
+ MyRunProcess('/bin/bash -e '+ExtractFilePath(Application.ExeName)+'/command.sh',true,false,process);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ MyRunProcess(command,true,false,process);
+ {$ENDIF}
+ // seek the output for the token
+ while (process.Running or (process.Output.NumBytesAvailable > 0)) and (not found) do
+ begin
+ Form1.LabelStatus.Caption:='waiting for the token...';
+ Form1.LabelStatus.Repaint;
+ if process.Output.NumBytesAvailable > 0 then
+ begin
+ // make sure that we don't read more data than we have allocated
+ // in the buffer
+ ReadSize := process.Output.NumBytesAvailable;
+ if ReadSize > SizeOf(Buffer) then
+ ReadSize := SizeOf(Buffer);
+ // now read the output into the buffer
+ process.Output.Read(Buffer[0], ReadSize);
+ // and if it contains a token, then open a URL in the browser
+ s:=Buffer;
+ if pos('?token=',s)>0 then
+ begin
+ s:=copy(s,pos('?token=',s)+7,48);
+ Form1.LabelStatus.Caption:='opening browser...';
+ Form1.LabelStatus.Repaint;
+ OpenUrl('http://127.0.0.1:'+IntToStr(port)+'?token='+s);
+ found:=true;
+ end;
+ end;
+ end;
+ end
+ else
+ ShowErrorDisconnectedIOW;
+ Form1.Timer1Timer(Form1);
+ sl.Free;
+ Form1.LabelStatus.Caption:='';
+end;
+
+procedure StartJupyterlabRemote(hostname: String='haumea1');
+var
+ sl, sl1: TStringList;
+ username, command, s: String;
+ i, userid, port, ReadSize: Integer;
+ process, process1: TProcess;
+ Buffer: array[0..100000] of char;
+ found, hasGPU: boolean;
+ portBlocked: Boolean;
+ F: TextFile;
+ remoteBasepath, nodeName: String;
+begin
+ {$IFDEF WINDOWS}
+ // first we need to start the ssh-agent to enable passwordless login
+ MyRunProcess('powershell -command "& {&''Start-Service'' ssh-agent}"',true,true);
+ {$ENDIF}
+ {$IFDEF Linux}
+ // we need to unlock a key to allow passwordless login
+ if (hostname='haumea1') and (haumea_unlocked=false) then unlock_haumea;
+ if (hostname='blogin') and (hlrn_unlocked=false) then unlock_hlrn;
+ if (hostname='glogin') and (hlrn_unlocked=false) then unlock_hlrn;
+ {$ENDIF}
+ if hostname='haumea1' then remoteBasepath:='/data';
+ if hostname='blogin' then remoteBasepath:='/scratch/usr';
+ if hostname='glogin' then remoteBasepath:='/scratch/usr';
+ // Show Form2 to enter details for parallel processing and job registration
+ Form2.Edit1.Text:=hostname;
+ Form2.Label2.Visible:=false;
+ Form2.SpinEdit1.Visible:=false;
+ Form2.Label3.Visible := false;
+ Form2.Edit2.Visible := false;
+ Form2.Label4.Visible:=false;
+ Form2.Edit3.Visible:=false;
+ Form2.SpinEdit2.Value:=8;
+ Form2.Label6.Visible:=true;
+ Form2.ComboBox1.Visible:=true;
+ Form2.ComboBox1.Items.Clear;
+ if hostname='haumea1' then
+ begin
+ Form2.ComboBox1.Items.Add('compute');
+ Form2.ComboBox1.Items.Add('requeue');
+ end
+ else if hostname='blogin' then
+ begin
+ Form2.ComboBox1.Items.Add('standard96');
+ Form2.ComboBox1.Items.Add('standard96:test');
+ Form2.ComboBox1.Items.Add('large96');
+ Form2.ComboBox1.Items.Add('large96:test');
+ Form2.ComboBox1.Items.Add('huge96');
+ end
+ else if hostname='glogin' then
+ begin
+ Form2.ComboBox1.Items.Add('medium40');
+ Form2.ComboBox1.Items.Add('medium40:test');
+ Form2.ComboBox1.Items.Add('large40');
+ Form2.ComboBox1.Items.Add('large40:test');
+ Form2.ComboBox1.Items.Add('standard96');
+ Form2.ComboBox1.Items.Add('standard96:test');
+ Form2.ComboBox1.Items.Add('large96');
+ Form2.ComboBox1.Items.Add('large96:test');
+ Form2.ComboBox1.Items.Add('huge96');
+ end;
+ Form2.ComboBox1.ItemIndex:=0;
+ Form2.Tag:=0;
+ Form2.ShowModal;
+ Application.ProcessMessages;
+ if Form2.Tag <> 1 then exit;
+
+ Form1.LabelStatus.Caption:='connecting to '+hostname+'...';
+ Form1.LabelStatus.Repaint;
+ found:=false; // we will try to find the token in the output
+ // get remote location username and userid
+ sl:=MyRunProcess('ssh '+hostname+' whoami ; id -u',true,true);
+ if sl.count=2 then
+ begin
+ username:=trim(sl[0]);
+ userid:=StrToInt(sl[1]);
+ port:=PortFromUserid(userid,'REMOTE');
+ // write the jupyterlab script
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/jupyterlab_remote.sh');
+ rewrite(F);
+ writeln(F,'#!/bin/bash');
+ writeln(F,'#');
+ writeln(F,'#SBATCH --job-name=jupyter');
+ writeln(F,'#SBATCH --output='+remoteBasepath+'/%u/.jupyter_out.txt');
+ writeln(F,'#SBATCH --nodes 1');
+ writeln(F,'#SBATCH --ntasks=1');
+ writeln(F,'#SBATCH -t '+IntToStr(Form2.SpinEdit2.Value)+':00:00');
+ writeln(F,'#SBATCH -p '+Form2.ComboBox1.Items[Form2.ComboBox1.ItemIndex]);
+ writeln(F,'');
+ writeln(F,'source ~/.bashrc');
+ writeln(F,'PATH=$PATH:$HOME/.local/bin:$HOME/bin');
+ writeln(F,'');
+ writeln(F,'export PATH');
+ writeln(F,'');
+ if hostname='haumea1' then
+ begin
+ writeln(F,'export MODULEPATH=$MODULEPATH:/data/iow/iow-modules/DATAFORMATS');
+ writeln(F,'export MODULEPATH=$MODULEPATH:/data/iow/iow-modules/VISUALISATION');
+ writeln(F,'export MODULEPATH=$MODULEPATH:/data/iow/iow-modules/MPI');
+ writeln(F,'export MODULEPATH=$MODULEPATH:/data/iow/iow-modules/NUMERICS');
+ writeln(F,'module load miniconda3');
+ end
+ else
+ writeln(F,'module load anaconda3');
+ writeln(F,'');
+ writeln(F,'export OMP_NUM_THREADS=40');
+ writeln(F,'export KMP_AFFINITY=compact,1,0,granularity=fine');
+ writeln(F,'export MKLDNN_VERBOSE=1');
+ writeln(F,'export MYJUPYTERPORT='+IntToStr(port));
+ writeln(F,'export XDG_RUNTIME_DIR="'+remoteBasepath+'/$USER"');
+ writeln(F,'hostname > '+remoteBasepath+'/'+username+'/.jupyterlab_hostname.txt');
+ writeln(F,'jupyter lab --port $MYJUPYTERPORT --ip 0.0.0.0 --no-browser --notebook-dir="'+remoteBasepath+'/$USER"');
+ closefile(F);
+ Form1.LabelStatus.Caption:='transferring job script...';
+ Form1.LabelStatus.Repaint;
+ // copy the jupyterlab script to IOW
+ command := 'scp '+ExtractFilePath(application.ExeName)+'/jupyterlab_remote.sh '+hostname+':'+remoteBasepath+'/'+username;
+ MyRunProcess(command,true,true);
+ // convert to unix line endings and set execute permissions
+ command := 'ssh '+hostname+' sed -i ''s/\r//'' '+remoteBasepath+'/'+username+'/jupyterlab_remote.sh';
+ MyRunProcess(command,true,true);
+ command := 'ssh '+hostname+' chmod u+x '+remoteBasepath+'/'+username+'/jupyterlab_remote.sh';
+ MyRunProcess(command,true,true);
+ Form1.LabelStatus.Caption:='checking if port is free...';
+ Form1.LabelStatus.Repaint;
+ // check if port is in use
+ portBlocked:=false;
+ command := 'ssh '+hostname+' fuser '+IntToStr(port)+'/tcp';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>=1 then portBlocked:=true;
+ sl1.Free;
+ if portBlocked then
+ if MessageDlg('port in use','Port '+IntToStr(port)+' is already in use by another process. Kill that process?',mtConfirmation,[mbYes,mbAbort],0,mbAbort)=mrYes then
+ begin
+ command := 'ssh '+hostname+' fuser -k -n tcp '+IntToStr(port);
+ MyRunProcess(command,true,true);
+ sleep(1000);
+ end
+ else
+ exit;
+ // delete output files of an old jupyterlab job if exists
+ Form1.LabelStatus.Caption:='cleaning up old files...';
+ Form1.LabelStatus.Repaint;
+ command := 'ssh -q '+hostname+' "if [ -f '+remoteBasepath+'/'+username+
+ '/.jupyter_out.txt ] ; then rm '+remoteBasepath+'/'+username+'/.jupyter_out.txt ; fi"';
+ {$IFDEF LINUX}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,command);
+ closefile(F);
+ MyRunProcess('/bin/bash -e '+ExtractFilePath(Application.ExeName)+'/command.sh',true,true);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ MyRunProcess(command,true,true);
+ {$ENDIF}
+ command := 'ssh -q '+hostname+' "if [ -f '+remoteBasepath+'/'+username+
+ '/.jupyterlab_hostname.txt ] ; then rm '+remoteBasepath+'/'+username+'/.jupyterlab_hostname.txt ; fi"';
+ {$IFDEF LINUX}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,command);
+ closefile(F);
+ MyRunProcess('/bin/bash -e '+ExtractFilePath(Application.ExeName)+'/command.sh',true,true);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ MyRunProcess(command,true,true);
+ {$ENDIF}
+ // prepare the command to submit the job and run it
+ Form1.LabelStatus.Caption:='submitting job...';
+ Form1.LabelStatus.Repaint;
+ command := 'ssh '+hostname+' sbatch '+remoteBasepath+'/'+username+'/jupyterlab_remote.sh';
+ MyRunProcess(command,true,true);
+ // wait until the output file exists
+ Form1.LabelStatus.Caption:='waiting for job to start...';
+ Form1.LabelStatus.Repaint;
+ found:=false;
+ while not found do
+ begin
+ command:='ssh '+hostname+' ls '+remoteBasepath+'/'+username+'/.jupyter_out.txt';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>0 then found:=true;
+ sl1.Free;
+ sleep(1000);
+ end;
+ // then, get the node name
+ Form1.LabelStatus.Caption:='job is running, checking on which node...';
+ Form1.LabelStatus.Repaint;
+ command:='ssh '+hostname+' cat '+remoteBasepath+'/'+username+'/.jupyterlab_hostname.txt';
+ sl1:=MyRunProcess(command,true,true);
+ while sl1.count<1 do
+ begin
+ sleep(500);
+ sl1.Free;
+ sl1:=MyRunProcess(command,true,true);
+ end;
+ nodeName:=sl1[0];
+ // open SSH tunnel for the ports
+ Form1.LabelStatus.Caption:='opening ssh tunnel...';
+ Form1.LabelStatus.Repaint;
+ command := 'ssh -L '+IntToStr(port)+':localhost:'+IntToStr(port)+' '+hostname+
+ ' ssh -L '+IntToStr(port)+':localhost:'+IntToStr(port)+
+ ' -N '+nodeName+' & 2>/dev/null';
+ {$IFDEF LINUX}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,command);
+ closefile(F);
+ MyRunProcess('/bin/bash -e '+ExtractFilePath(Application.ExeName)+'/command.sh',true,false,process);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ MyRunProcess(command,true,false,process);
+ {$ENDIF}
+ // grep until we find the token
+ Form1.LabelStatus.Caption:='waiting for token...';
+ Form1.LabelStatus.Repaint;
+ command := 'ssh '+hostname+' grep "token" '+remoteBasepath+'/'+username+'/.jupyter_out.txt 2>/dev/null';
+ found:=false;
+ while not found do
+ begin
+ sl1 := MyRunProcess(command,true,true);
+ for i:=0 to sl1.Count-1 do
+ if pos('?token=',sl1[i])>0 then
+ begin
+ s:=copy(sl1[i],pos('?token=',sl1[i])+7,48);
+ if not found then
+ begin
+ Form1.LabelStatus.Caption:='opening browser...';
+ Form1.LabelStatus.Repaint;
+ OpenUrl('http://127.0.0.1:'+IntToStr(port)+'?token='+s);
+ end;
+ found:=true;
+ end;
+ sleep(1000);
+ end;
+ end
+ else
+ if hostname='haumea1' then ShowErrorDisconnectedHaumea;
+ sl.Free;
+ Form1.LabelStatus.Caption:='';
+end;
+
+procedure StartConsoleIOW(hostname: String);
+var
+ sl: TStringList;
+ F: TextFile;
+begin
+ // get IOW username and userid
+ sl:=MyRunProcess('ssh rdpserv whoami ; id -u',true,true);
+ if sl.count=2 then
+ begin
+ // run command in console
+ {$IFDEF Linux}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,'ssh -t -X rdpserv ssh -X '+hostname);
+ closefile(F);
+ MyRunProcess(FavouriteTerminalStart+' '+ExtractFilePath(application.ExeName)+'/command.sh'+FavouriteTerminalEnd,false,false);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ ShellExecute(0,nil, PChar('cmd'),PChar('/C ssh -t rdpserv ssh '+hostname),nil,1);
+ {$ENDIF}
+ end
+ else
+ ShowErrorDisconnectedIOW;
+ sl.Free;
+end;
+
+procedure StartConsoleRemote(hostname: String='haumea1');
+var
+ sl: TStringList;
+ F: TextFile;
+begin
+ // first we need to start the ssh-agent to enable passwordless login
+ {$IFDEF WINDOWS}
+ MyRunProcess('powershell -command "& {&''Start-Service'' ssh-agent}"',true,true);
+ {$ENDIF}
+ {$IFDEF Linux}
+ // we need to unlock a key to allow passwordless login
+ if (hostname='haumea1') and (haumea_unlocked=false) then unlock_haumea;
+ if (hostname='blogin') and (hlrn_unlocked=false) then unlock_hlrn;
+ if (hostname='glogin') and (hlrn_unlocked=false) then unlock_hlrn;
+ {$ENDIF}
+ // get haumea username and userid
+ sl:=MyRunProcess('ssh '+hostname+' whoami ; id -u',true,true);
+ if sl.count=2 then
+ begin
+ // run command in console
+ {$IFDEF Linux}
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/command.sh');
+ Rewrite(F);
+ writeln(F,'ssh -X '+hostname);
+ closefile(F);
+ MyRunProcess(FavouriteTerminalStart+' '+ExtractFilePath(application.ExeName)+'/command.sh'+FavouriteTerminalEnd,false,false);
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ ShellExecute(0,nil, PChar('cmd'),PChar('/C ssh '+hostname),nil,1);
+ {$ENDIF}
+ end
+ else
+ if hostname='haumea1' then ShowErrorDisconnectedHaumea;
+ sl.Free;
+end;
+
+procedure StartXpraIOW(hostname: String);
+var
+ sl, sl1: TStringList;
+ username, command, s: String;
+ userid, port, ReadSize: Integer;
+ process: TProcess;
+ Buffer: array[0..100000] of char;
+ found: boolean;
+ portBlocked: Boolean;
+begin
+ found:=false; // we will seek the output to find out when the server is running
+ // get IOW username and userid
+ Form1.LabelStatus.Caption:='connecting to IOW...';
+ Form1.LabelStatus.Repaint;
+ sl:=MyRunProcess('ssh rdpserv whoami ; id -u',true,true);
+ if sl.count=2 then
+ begin
+ username:=trim(sl[0]);
+ userid:=StrToInt(sl[1]);
+ port:=PortFromUserid(userid,'IOW','XPRA');
+ Form1.LabelStatus.Caption:='sending xpra script...';
+ Form1.LabelStatus.Repaint;
+ // copy the xpra script to IOW
+ command := 'scp '+ExtractFilePath(application.ExeName)+'/xpra_iow.sh rdpserv:/home/nis/'+username;
+ MyRunProcess(command,true,true);
+ // set execute permissions
+ command := 'ssh rdpserv chmod u+x /home/nis/'+username+'/xpra_iow.sh';
+ MyRunProcess(command,true,true);
+ // check if port is in use
+ Form1.LabelStatus.Caption:='checking if port is free...';
+ Form1.LabelStatus.Repaint;
+ portBlocked:=false;
+ command := 'ssh rdpserv fuser '+IntToStr(port)+'/tcp';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>=1 then portBlocked:=true;
+ sl1.Free;
+ command := 'ssh rdpserv ssh '+hostname+' fuser '+IntToStr(port)+'/tcp';
+ sl1:=MyRunProcess(command,true,true);
+ if sl1.Count>=1 then portBlocked:=true;
+ sl1.Free;
+ if portBlocked then
+ if MessageDlg('port in use','Port '+IntToStr(port)+' is already in use by another process. Kill that process?',mtConfirmation,[mbYes,mbAbort],0,mbAbort)=mrYes then
+ begin
+ command := 'ssh rdpserv fuser -k -n tcp '+IntToStr(port);
+ MyRunProcess(command,true,true);
+ command := 'ssh rdpserv ssh '+hostname+' fuser -k -n tcp '+IntToStr(port);
+ MyRunProcess(command,true,true);
+ sleep(1000);
+ end
+ else
+ exit;
+ // prepare the xpra command and run it
+ Form1.LabelStatus.Caption:='starting xpra...';
+ Form1.LabelStatus.Repaint;
+ command := 'ssh -t -X rdpserv -L '+IntToStr(port)+':localhost:'+IntToStr(port)+
+ ' ssh -t -X '+hostname+' -L '+IntToStr(port)+':localhost:'+IntToStr(port)+
+ ' /bin/bash -c /home/nis/'+username+'/xpra_iow.sh 2>&1';
+ MyRunProcess(command,true,false,process);
+ // seek the output for the term "running"
+ Form1.LabelStatus.Caption:='waiting until xpra has started...';
+ Form1.LabelStatus.Repaint;
+ while (process.Running or (process.Output.NumBytesAvailable > 0)) and (not found) do
+ begin
+ if process.Output.NumBytesAvailable > 0 then
+ begin
+ // make sure that we don't read more data than we have allocated
+ // in the buffer
+ ReadSize := process.Output.NumBytesAvailable;
+ if ReadSize > SizeOf(Buffer) then
+ ReadSize := SizeOf(Buffer);
+ // now read the output into the buffer
+ process.Output.Read(Buffer[0], ReadSize);
+ // and if it contains a token, then open a URL in the browser
+ s:=Buffer;
+ if pos('running',s)>0 then
+ begin
+ sleep(3000); //wait until it really starts
+ Form1.LabelStatus.Caption:='opening browser...';
+ Form1.LabelStatus.Repaint;
+ OpenUrl('http://127.0.0.1:'+IntToStr(port));
+ found:=true;
+ end;
+ end;
+ end;
+ end
+ else
+ ShowErrorDisconnectedIOW;
+ sl.Free;
+ Form1.LabelStatus.Caption:='';
+end;
+
+{ TForm1 }
+
+procedure TForm1.Button10Click(Sender: TObject);
+{$IFDEF Linux}
+var
+ found: Boolean;
+{$ENDIF}
+begin
+ {$IFDEF Linux}
+ found:=false;
+ if DirectoryExists('/public') then
+ if funIstVerzeichnisLeer('/public')=false then
+ begin
+ MyRunProcess('gdk-open /public',false,false);
+ end;
+ if not found then
+ ShowMessage('"/public" directory is not mounted on your machine. Please add it to the /etc/fstab or open a console on one of the phy servers and run "cd /public"')
+
+ {$ENDIF}
+ {$IFDEF WINDOWS}
+ MyRunProcess('explorer \\netapp1.io-warnemuende.de\public',false,false);
+ {$ENDIF}
+end;
+
+procedure TForm1.Button11Click(Sender: TObject);
+begin
+ OpenUrl('https://git.io-warnemuende.de/');
+end;
+
+procedure TForm1.Button12Click(Sender: TObject);
+begin
+ OpenNmonUrl('PHY-2');
+end;
+
+procedure TForm1.Button13Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-2');
+end;
+
+procedure TForm1.Button14Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-2');
+end;
+
+procedure TForm1.Button15Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-2');
+end;
+
+procedure TForm1.Button16Click(Sender: TObject);
+begin
+ OpenNmonUrl('PHY-3');
+end;
+
+procedure TForm1.Button17Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-3');
+end;
+
+procedure TForm1.Button18Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-3');
+end;
+
+procedure TForm1.Button19Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-3');
+end;
+
+procedure TForm1.Button1Click(Sender: TObject);
+begin
+ MyRunProcess(FavouriteTerminalStart+' '+ExtractFilePath(application.ExeName)+'/command.sh'+FavouriteTerminalEnd,false,false);
+ OpenUrl('http://phywiki.io-warnemuende.de/pub/Server/IntroductionNewColleagues/new_colleagues_introduction_it.pdf');
+end;
+
+procedure TForm1.Button20Click(Sender: TObject);
+begin
+ OpenNmonUrl('PHY-4');
+end;
+
+procedure TForm1.Button21Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-4');
+end;
+
+procedure TForm1.Button22Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-4');
+end;
+
+procedure TForm1.Button23Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-4');
+end;
+
+procedure TForm1.Button24Click(Sender: TObject);
+begin
+ OpenNmonUrl('PHY-10');
+end;
+
+procedure TForm1.Button25Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-10');
+end;
+
+procedure TForm1.Button26Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-10');
+end;
+
+procedure TForm1.Button27Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-10');
+end;
+
+procedure TForm1.Button28Click(Sender: TObject);
+begin
+ OpenNmonUrl('PHY-11');
+end;
+
+procedure TForm1.Button29Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-11');
+end;
+
+procedure TForm1.Button2Click(Sender: TObject);
+begin
+ OpenNmonUrl('phy-1');
+end;
+
+procedure TForm1.Button30Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-11');
+end;
+
+procedure TForm1.Button31Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-11');
+end;
+
+procedure TForm1.Button32Click(Sender: TObject);
+begin
+ OpenUrl('http://phywiki.io-warnemuende.de/do/view/Server/UniRostockClusterDocumentationList');
+end;
+
+procedure TForm1.Button33Click(Sender: TObject);
+begin
+ StartJupyterlabRemote('haumea1');
+end;
+
+procedure TForm1.Button34Click(Sender: TObject);
+begin
+ StartConsoleRemote('haumea1');
+end;
+
+procedure TForm1.Button35Click(Sender: TObject);
+begin
+ OpenUrl('http://phywiki.io-warnemuende.de/do/view/Server/HLRNApplicationProcedure');
+end;
+
+procedure TForm1.Button36Click(Sender: TObject);
+begin
+ StartJupyterlabRemote('blogin');
+end;
+
+procedure TForm1.Button37Click(Sender: TObject);
+begin
+ StartConsoleRemote('blogin');
+end;
+
+procedure TForm1.Button38Click(Sender: TObject);
+begin
+ StartJupyterlabRemote('glogin');
+end;
+
+procedure TForm1.Button39Click(Sender: TObject);
+begin
+ StartConsoleRemote('glogin');
+end;
+
+procedure TForm1.Button3Click(Sender: TObject);
+begin
+ StartJupyterlabIOW('phy-1');
+end;
+
+procedure TForm1.Button40Click(Sender: TObject);
+begin
+ timer1timer(self);
+end;
+
+procedure TForm1.Button4Click(Sender: TObject);
+begin
+ StartConsoleIOW('phy-1');
+end;
+
+procedure TForm1.Button5Click(Sender: TObject);
+begin
+ StartXpraIOW('phy-1');
+end;
+
+procedure TForm1.Button6Click(Sender: TObject);
+begin
+ OpenUrl('http://phywiki.io-warnemuende.de/');
+end;
+
+procedure TForm1.Button7Click(Sender: TObject);
+begin
+ OpenUrl('https://email.io-warnemuende.de/');
+end;
+
+procedure TForm1.Button8Click(Sender: TObject);
+begin
+ OpenUrl('https://chat.io-warnemuende.de/');
+end;
+
+procedure TForm1.Button9Click(Sender: TObject);
+begin
+ OpenURL('https://owncloud.io-warnemuende.de')
+end;
+
+procedure TForm1.FormCreate(Sender: TObject);
+var
+ F: TextFile;
+begin
+ haumea_unlocked:=false;
+ hlrn_unlocked:=false;
+ favouriteTerminalStart:='xterm -e ';
+ favouriteTerminalEnd:='';
+ if FileExists(ExtractFilePath(Application.ExeName)+'/favourite_terminal.txt') then
+ begin
+ AssignFile(F,ExtractFilePath(Application.ExeName)+'/favourite_terminal.txt');
+ Reset(F);
+ if not EOF(F) then
+ readln(F,favouriteTerminalStart);
+ readln(F,favouriteTerminalEnd);
+ closefile(F);
+ end;
+end;
+
+procedure TForm1.FormShow(Sender: TObject);
+begin
+ Timer1timer(self);
+end;
+
+procedure TForm1.Label6Click(Sender: TObject);
+begin
+
+end;
+
+procedure TForm1.Memo1Change(Sender: TObject);
+begin
+
+end;
+
+procedure TForm1.Timer1Timer(Sender: TObject);
+var
+ AStringList: TStringList;
+begin
+ AStringList:=MyRunProcess('ssh rdpserv /sw/sys/scripts/combine_machine_usage',true,true);
+ if AStringList.Count>0 then
+ begin
+ // fill the information in into the GUI
+ ShowUsageInfo(AStringList,LabelCPU1, LabelMEM1, Memo1);
+ ShowUsageInfo(AStringList,LabelCPU2, LabelMEM2, Memo2);
+ ShowUsageInfo(AStringList,LabelCPU3, LabelMEM3, Memo3);
+ ShowUsageInfo(AStringList,LabelCPU4, LabelMEM4, Memo4);
+ ShowUsageInfo(AStringList,LabelCPU5, LabelMEM5, Memo5);
+ ShowUsageInfo(AStringList,LabelCPU6, LabelMEM6, Memo6);
+ end
+ else
+ begin
+ ShowErrorDisconnectedIOW;
+ Halt;
+ end;
+ AStringList.Free;
+end;
+
+end.
+
diff --git a/unit2.lfm b/unit2.lfm
new file mode 100644
index 0000000..d64be5a
--- /dev/null
+++ b/unit2.lfm
@@ -0,0 +1,143 @@
+object Form2: TForm2
+ Left = 578
+ Height = 344
+ Top = 344
+ Width = 456
+ Caption = 'phyportal - start jupyter lab'
+ ClientHeight = 344
+ ClientWidth = 456
+ Color = clNavy
+ Font.Height = -14
+ Font.Name = 'Open Sans'
+ LCLVersion = '2.2.0.4'
+ object Shape1: TShape
+ Left = 6
+ Height = 326
+ Top = 6
+ Width = 442
+ Brush.Color = clSilver
+ Pen.Style = psClear
+ end
+ object Label1: TLabel
+ Left = 18
+ Height = 20
+ Top = 19
+ Width = 132
+ Caption = 'start jupyter lab on:'
+ end
+ object Edit1: TEdit
+ Left = 83
+ Height = 30
+ Top = 38
+ Width = 311
+ Color = clSilver
+ Enabled = False
+ TabOrder = 0
+ Text = 'Edit1'
+ end
+ object Label2: TLabel
+ Left = 18
+ Height = 20
+ Top = 77
+ Width = 225
+ Caption = 'CPU cores for parallel computing:'
+ end
+ object SpinEdit1: TSpinEdit
+ Left = 83
+ Height = 30
+ Top = 96
+ Width = 122
+ MaxValue = 128
+ MinValue = 1
+ TabOrder = 1
+ Value = 1
+ end
+ object Label3: TLabel
+ Left = 237
+ Height = 20
+ Top = 77
+ Width = 168
+ Caption = 'Which GPUs will you use:'
+ end
+ object Edit2: TEdit
+ Left = 262
+ Height = 30
+ Top = 96
+ Width = 134
+ TabOrder = 2
+ Text = 'no GPUs'
+ end
+ object Label4: TLabel
+ Left = 16
+ Height = 20
+ Top = 142
+ Width = 359
+ Caption = 'What will you do (leave empty for no job registration):'
+ end
+ object Edit3: TEdit
+ Left = 83
+ Height = 30
+ Top = 160
+ Width = 313
+ TabOrder = 3
+ Text = 'Edit3'
+ end
+ object Label5: TLabel
+ Left = 17
+ Height = 20
+ Top = 198
+ Width = 119
+ Caption = 'How many hours?'
+ end
+ object BitBtn1: TBitBtn
+ Left = 82
+ Height = 30
+ Top = 275
+ Width = 146
+ Default = True
+ DefaultCaption = True
+ Kind = bkOK
+ ModalResult = 1
+ OnClick = BitBtn1Click
+ TabOrder = 4
+ end
+ object BitBtn2: TBitBtn
+ Left = 262
+ Height = 30
+ Top = 274
+ Width = 134
+ Cancel = True
+ DefaultCaption = True
+ Kind = bkCancel
+ ModalResult = 2
+ OnClick = BitBtn2Click
+ TabOrder = 5
+ end
+ object SpinEdit2: TSpinEdit
+ Left = 84
+ Height = 30
+ Top = 218
+ Width = 121
+ MaxValue = 1000
+ MinValue = 1
+ TabOrder = 6
+ Value = 1
+ end
+ object Label6: TLabel
+ Left = 233
+ Height = 20
+ Top = 198
+ Width = 49
+ Caption = 'Queue:'
+ end
+ object ComboBox1: TComboBox
+ Left = 243
+ Height = 32
+ Top = 218
+ Width = 150
+ ItemHeight = 0
+ OnSelect = ComboBox1Select
+ Style = csDropDownList
+ TabOrder = 7
+ end
+end
diff --git a/unit2.pas b/unit2.pas
new file mode 100644
index 0000000..26142ca
--- /dev/null
+++ b/unit2.pas
@@ -0,0 +1,69 @@
+unit Unit2;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
+ Spin, Buttons;
+
+type
+
+ { TForm2 }
+
+ TForm2 = class(TForm)
+ BitBtn1: TBitBtn;
+ BitBtn2: TBitBtn;
+ ComboBox1: TComboBox;
+ Edit1: TEdit;
+ Edit2: TEdit;
+ Edit3: TEdit;
+ Label1: TLabel;
+ Label2: TLabel;
+ Label3: TLabel;
+ Label4: TLabel;
+ Label5: TLabel;
+ Label6: TLabel;
+ Shape1: TShape;
+ SpinEdit1: TSpinEdit;
+ SpinEdit2: TSpinEdit;
+ procedure BitBtn1Click(Sender: TObject);
+ procedure BitBtn2Click(Sender: TObject);
+ procedure ComboBox1Select(Sender: TObject);
+ private
+
+ public
+
+ end;
+
+var
+ Form2: TForm2;
+
+implementation
+
+{$R *.lfm}
+
+{ TForm2 }
+
+procedure TForm2.ComboBox1Select(Sender: TObject);
+begin
+ // in a queue called "test", jobs can run no longer than 1 hour by default
+ if ComboBox1.Items.Count>0 then
+ if pos('test',ComboBox1.Items[ComboBox1.ItemIndex])>0 then
+ SpinEdit2.Value:=1;
+end;
+
+procedure TForm2.BitBtn1Click(Sender: TObject);
+begin
+ Form2.Tag:=1;
+ Form2.Close;
+end;
+
+procedure TForm2.BitBtn2Click(Sender: TObject);
+begin
+ Form2.Close;
+end;
+
+end.
+