pinokiod 3.6.29 → 3.6.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server/index.js CHANGED
@@ -28,6 +28,8 @@ const multer = require('multer');
28
28
 
29
29
  const ejs = require('ejs');
30
30
 
31
+ const DEFAULT_PORT = 42000
32
+
31
33
  const ex = fn => (req, res, next) => {
32
34
  Promise.resolve(fn(req, res, next)).catch(next);
33
35
  };
@@ -45,7 +47,8 @@ class Server {
45
47
  constructor(config) {
46
48
  this.tabs = {}
47
49
  this.agent = config.agent
48
- this.port = config.port
50
+ this.port = DEFAULT_PORT
51
+ // this.port = config.port
49
52
  this.kernel = new Kernel(config.store)
50
53
  // this.tunnels = {}
51
54
  this.version = {
@@ -83,30 +86,6 @@ class Server {
83
86
  exists (s) {
84
87
  return new Promise(r=>fs.access(s, fs.constants.F_OK, e => r(!e)))
85
88
  }
86
- winBuildNumber() {
87
- let osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
88
- let buildNumber = 0;
89
- if (osVersion && osVersion.length === 4) {
90
- buildNumber = parseInt(osVersion[3]);
91
- }
92
- return buildNumber;
93
- }
94
- compatible() {
95
- if (this.kernel.platform === "win32") {
96
- let buildNumber = this.winBuildNumber()
97
- if (buildNumber < 18309) {
98
- // must use conpty for node-pty, and conpty is only supported in win>=18309
99
- console.log("Windows buildNumber", buildNumber)
100
- throw new Error(`Pinokio supports Windows release 18309 and up (current system: ${buildNumber}`)
101
- }
102
-
103
- // if (buildNumber > 25000) {
104
- // console.log("Windows buildNumber", buildNumber)
105
- // throw new Error(`Pinokio does not currently support Windows Canary (versions 25000 and up). The current system is ${buildNumber}`)
106
- //
107
- // }
108
- }
109
- }
110
89
  getMemory(filepath) {
111
90
  let localMem = this.kernel.memory.local[filepath]
112
91
  let globalMem = this.kernel.memory.global[filepath]
@@ -142,7 +121,7 @@ class Server {
142
121
  return items.map((x) => {
143
122
  let name
144
123
  let description
145
- let icon
124
+ let icon = "/pinokio-black.png"
146
125
  let uri
147
126
  if (meta) {
148
127
  let m = meta[x.name]
@@ -151,7 +130,8 @@ class Server {
151
130
  if (m && m.icon) {
152
131
  icon = m.icon
153
132
  } else {
154
- icon = null
133
+ icon = "/pinokio-black.png"
134
+ //icon = null
155
135
  }
156
136
  uri = x.name
157
137
  } else {
@@ -191,6 +171,60 @@ class Server {
191
171
  }
192
172
  })
193
173
  }
174
+ async init_shells(dir) {
175
+ let current = this.kernel.path(dir, "SHELLS")
176
+ let exists = await this.exists(current)
177
+ if (exists) {
178
+ // SHELLS folder exists, skip
179
+ } else {
180
+ // if SHELLS doesn't exist, need to create one
181
+ // 1. if _SHELLS exists, create SHELLS by copying the contents of _SHELLS into SHELLS
182
+ // 2. if _SHELLS doesn't exist, just write SHELLS
183
+ // if _ENVIRONMENT exists,
184
+ let _shells = this.kernel.path(dir, "_SHELLS")
185
+ let _dest_shells = this.kernel.path(dir, "SHELLS")
186
+ await fs.promises.mkdir(_dest_shells).catch((e) => { })
187
+
188
+ // write _.json
189
+ let defaultShell = path.resolve(_dest_shells, "_.json")
190
+ let default_shell_exists = await this.exists(_shells)
191
+ if (!default_shell_exists) {
192
+ await fs.promises.writeFile(defaultShell, JSON.stringify({
193
+ run: [{
194
+ method: "shell.run",
195
+ params: {
196
+ message: [
197
+ "npm install -g @anthropic-ai/claude-code",
198
+ ],
199
+ }
200
+ }, {
201
+ method: "shell.run",
202
+ params: {
203
+ message: [
204
+ "claude"
205
+ ],
206
+ input: true
207
+ }
208
+ }]
209
+ }, null, 2))
210
+ }
211
+
212
+ let _exists = await this.exists(_shells)
213
+ if (_exists) {
214
+ let files = await fs.promises.readdir(_shells)
215
+ for(let file of files) {
216
+ try {
217
+ let src = path.resolve(_shells, file)
218
+ let dest = path.resolve(_dest_shells, file)
219
+ await fs.promises.cp(src, dest, { errorOnExist: true, force: false });
220
+ } catch (err) {
221
+ }
222
+ }
223
+ // } else {
224
+ // await fs.cp(_shells, _dest_shells, { recursive: true });
225
+ }
226
+ }
227
+ }
194
228
  async init_env(env_dir_path, options) {
195
229
  let current = this.kernel.path(env_dir_path, "ENVIRONMENT")
196
230
  // if environment.json doesn't exist,
@@ -298,6 +332,12 @@ class Server {
298
332
  }
299
333
  let platform = os.platform()
300
334
 
335
+ if (config.icon) {
336
+ config.icon = `${rawpath}/${config.icon}?raw=true`
337
+ } else {
338
+ config.icon = "/pinokio-black.png"
339
+ }
340
+
301
341
 
302
342
  // get all memory variable stied to the current repository
303
343
  let api_path = this.kernel.path("api", name)
@@ -320,6 +360,7 @@ class Server {
320
360
 
321
361
  await this.init_env("api/" + name)
322
362
 
363
+ await this.init_shells("api/" + name)
323
364
 
324
365
  let mode = "run"
325
366
  if (req.query && req.query.mode) {
@@ -327,7 +368,34 @@ class Server {
327
368
  }
328
369
  const env = await this.kernel.env("api/" + name)
329
370
 
371
+ let shell_path = this.kernel.path("api", name, "SHELLS")
372
+ let shells = []
373
+ try {
374
+ shells = await fs.promises.readdir(shell_path)
375
+ shells = shells.filter((s) => {
376
+ return s.endsWith(".json")
377
+ })
378
+
379
+ let s = []
380
+ for(let shell of shells) {
381
+ try {
382
+ let p = path.resolve(shell_path, shell)
383
+ console.log("3p", p)
384
+ let json = (await this.kernel.loader.load(p)).resolved
385
+ console.log(">>", { json })
386
+ if (json && json.run && Array.isArray(json.run)) {
387
+ s.push(`/api/${name}/SHELLS/${shell}`)
388
+ }
389
+ } catch (e) {
390
+ }
391
+ }
392
+ shells = s
393
+ } catch (e) {
394
+ }
395
+ console.log({ shells })
396
+
330
397
  res.render("app", {
398
+ shells,
331
399
  error,
332
400
  env,
333
401
  mode,
@@ -479,84 +547,8 @@ class Server {
479
547
  display: ["form"]
480
548
  })
481
549
  } else if (pathComponents.length === 0 && req.query.mode === "download") {
482
-
483
- let requirements = [{
484
- name: "conda",
485
- }, {
486
- name: "git",
487
- }, {
488
- name: "zip",
489
- }, {
490
- name: "node",
491
- }, {
492
- name: "ffmpeg",
493
- // type: "conda",
494
- // name: "ffmpeg",
495
- // args: "-c conda-forge"
496
- }]
497
- let platform = os.platform()
498
- if (platform === "win32") {
499
- requirements.push({
500
- name: "registry"
501
- })
502
- requirements.push({
503
- name: "vs"
504
- })
505
- }
506
- if (platform === "darwin") {
507
- requirements.push({
508
- name: "brew"
509
- })
510
- }
511
- if (platform === "linux") {
512
- requirements.push({
513
- name: "gxx"
514
- })
515
- }
516
- if (this.kernel.gpu === "nvidia") {
517
- requirements.push({
518
- name: "cuda",
519
- })
520
- }
521
- requirements = requirements.concat([{
522
- name: "py"
523
- }, {
524
- name: "cloudflared"
525
- }, {
526
- name: "playwright"
527
- }, {
528
- name: "huggingface"
529
- }, {
530
- name: "uv"
531
- }])
532
-
533
- let requirements_pending = !this.kernel.bin.installed_initialized
534
-
535
-
536
- let install_required = true
537
- if (!requirements_pending) {
538
- install_required = false
539
- for(let i=0; i<requirements.length; i++) {
540
- let r = requirements[i]
541
- console.time(r.name)
542
- let installed = await this.installed(r)
543
- console.log({ r, installed })
544
- console.timeEnd(r.name)
545
- requirements[i].installed = installed
546
- if (!installed) {
547
- install_required = true
548
- }
549
- }
550
- }
551
-
552
- let error = null
553
- try {
554
- this.compatible()
555
- } catch (e) {
556
- error = e.message
557
- install_required = true
558
- }
559
-
550
+ let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check_bin()
551
+ console.log({ requirements, install_required, requirements_pending })
560
552
 
561
553
  res.render("download", {
562
554
  error,
@@ -916,7 +908,7 @@ class Server {
916
908
 
917
909
  let error = null
918
910
  try {
919
- this.compatible()
911
+ this.kernel.bin.compatible()
920
912
  } catch (e) {
921
913
  error = e.message
922
914
  install_required = true
@@ -1278,7 +1270,7 @@ class Server {
1278
1270
  //let name = (x.name.startsWith("0x") ? Buffer.from(x.name.slice(2), "hex").toString() : x.name)
1279
1271
  let name
1280
1272
  let description
1281
- let icon
1273
+ let icon = "/pinokio-black.png"
1282
1274
  let uri
1283
1275
  if (meta) {
1284
1276
  let m = meta[x.name]
@@ -1287,7 +1279,8 @@ class Server {
1287
1279
  if (m && m.icon) {
1288
1280
  icon = m.icon
1289
1281
  } else {
1290
- icon = null
1282
+ //icon = null
1283
+ icon = "/pinokio-black.png"
1291
1284
  }
1292
1285
  uri = x.name
1293
1286
  } else {
@@ -1886,76 +1879,76 @@ class Server {
1886
1879
  // 5. existing home is NOT set + new home is set => go through with the "home" setting procedure
1887
1880
  // 6. existing home is NOT set + new home is NOT set => don't touch anything => the homedir will be the default home
1888
1881
 
1889
- // 2. HOME
1890
- // 2.1. Check if the config includes NEW_HOME => if so,
1891
- // - move the HOME folder to NEW_HOME
1892
- // - set HOME=NEW_HOME
1893
- // - remove NEW_HOME
1894
- let existing_home = this.kernel.store.get("home")
1895
- let new_home = this.kernel.store.get("new_home")
1896
-
1897
- if (existing_home) {
1898
- let exists = await fse.pathExists(existing_home)
1899
- if (exists) {
1900
- if (new_home) {
1901
- let new_home_exists = await fse.pathExists(new_home)
1902
- if (new_home_exists) {
1903
- // - existing home is set
1904
- // - existing home exists
1905
- // - new home is set
1906
- // - new home exists already
1907
- // => delete store.new_home ==> will load at store.home
1908
- this.kernel.store.delete("new_home")
1909
- } else {
1910
- // - existing home is set
1911
- // - existing home exists
1912
- // - new home is set
1913
- // - new home does not exist
1914
- // => run mv()
1915
- // => update store.home
1916
- // => delete store.new_home
1917
- await this.mv(existing_home, new_home)
1918
- this.kernel.store.set("home", new_home)
1919
- this.kernel.store.delete("new_home")
1920
- }
1921
- } else {
1922
- // - existing home is set
1923
- // - existing home exists
1924
- // - new home is not set
1925
- // => This is most typical scenario => don't touch anything => the homedir will be the existing home
1926
- }
1927
- } else {
1928
- if (new_home) {
1929
- // - existing home is set
1930
- // - but the existing home path DOES NOT exist
1931
- // - new home is set
1932
- // => This is an invalid scenario => Just to avoid disaster, just delete store.home and delete store.new_home
1933
- // => the app will load at ~/pinokio
1934
- this.kernel.store.delete("home")
1935
- this.kernel.store.delete("new_home")
1936
- } else {
1937
- // - existing home is set
1938
- // - but the existing home path DOES NOT exist
1939
- // - new home is NOT set
1940
- // => This is an invalid scenario => just delete store.home
1941
- // => the app will load at ~/pinokio
1942
- this.kernel.store.delete("home")
1943
- }
1944
- }
1945
- } else {
1946
- if (new_home) {
1947
- // - existing home is NOT set
1948
- // - new home is set
1949
- // => update store.home
1950
- // => delete store.new_home
1951
- this.kernel.store.set("home", new_home)
1952
- this.kernel.store.delete("new_home")
1953
- } else {
1954
- // - existing home is NOT set
1955
- // - new home is NOT set
1956
- // => don't touch anything => will load at ~/pinokio
1957
- }
1958
- }
1882
+ // // 2. HOME
1883
+ // // 2.1. Check if the config includes NEW_HOME => if so,
1884
+ // // - move the HOME folder to NEW_HOME
1885
+ // // - set HOME=NEW_HOME
1886
+ // // - remove NEW_HOME
1887
+ // let existing_home = this.kernel.store.get("home")
1888
+ // let new_home = this.kernel.store.get("new_home")
1889
+ //
1890
+ // if (existing_home) {
1891
+ // let exists = await fse.pathExists(existing_home)
1892
+ // if (exists) {
1893
+ // if (new_home) {
1894
+ // let new_home_exists = await fse.pathExists(new_home)
1895
+ // if (new_home_exists) {
1896
+ // // - existing home is set
1897
+ // // - existing home exists
1898
+ // // - new home is set
1899
+ // // - new home exists already
1900
+ // // => delete store.new_home ==> will load at store.home
1901
+ // this.kernel.store.delete("new_home")
1902
+ // } else {
1903
+ // // - existing home is set
1904
+ // // - existing home exists
1905
+ // // - new home is set
1906
+ // // - new home does not exist
1907
+ // // => run mv()
1908
+ // // => update store.home
1909
+ // // => delete store.new_home
1910
+ // await this.mv(existing_home, new_home)
1911
+ // this.kernel.store.set("home", new_home)
1912
+ // this.kernel.store.delete("new_home")
1913
+ // }
1914
+ // } else {
1915
+ // // - existing home is set
1916
+ // // - existing home exists
1917
+ // // - new home is not set
1918
+ // // => This is most typical scenario => don't touch anything => the homedir will be the existing home
1919
+ // }
1920
+ // } else {
1921
+ // if (new_home) {
1922
+ // // - existing home is set
1923
+ // // - but the existing home path DOES NOT exist
1924
+ // // - new home is set
1925
+ // // => This is an invalid scenario => Just to avoid disaster, just delete store.home and delete store.new_home
1926
+ // // => the app will load at ~/pinokio
1927
+ // this.kernel.store.delete("home")
1928
+ // this.kernel.store.delete("new_home")
1929
+ // } else {
1930
+ // // - existing home is set
1931
+ // // - but the existing home path DOES NOT exist
1932
+ // // - new home is NOT set
1933
+ // // => This is an invalid scenario => just delete store.home
1934
+ // // => the app will load at ~/pinokio
1935
+ // this.kernel.store.delete("home")
1936
+ // }
1937
+ // }
1938
+ // } else {
1939
+ // if (new_home) {
1940
+ // // - existing home is NOT set
1941
+ // // - new home is set
1942
+ // // => update store.home
1943
+ // // => delete store.new_home
1944
+ // this.kernel.store.set("home", new_home)
1945
+ // this.kernel.store.delete("new_home")
1946
+ // } else {
1947
+ // // - existing home is NOT set
1948
+ // // - new home is NOT set
1949
+ // // => don't touch anything => will load at ~/pinokio
1950
+ // }
1951
+ // }
1959
1952
  }
1960
1953
  async setConfig(config) {
1961
1954
  let home = this.kernel.store.get("home")
@@ -1980,13 +1973,14 @@ class Server {
1980
1973
  throw new Error("Invalid path: " + config.home)
1981
1974
  }
1982
1975
 
1983
- // check if the destination already exists => throw error
1984
- let exists = await fse.pathExists(config.home)
1985
- if (exists) {
1986
- throw new Error(`The path ${config.home} already exists. Please remove the folder and retry`)
1987
- }
1976
+ // // check if the destination already exists => throw error
1977
+ // let exists = await fse.pathExists(config.home)
1978
+ // if (exists) {
1979
+ // throw new Error(`The path ${config.home} already exists. Please remove the folder and retry`)
1980
+ // }
1988
1981
 
1989
- this.kernel.store.set("new_home", config.home)
1982
+ //this.kernel.store.set("new_home", config.home)
1983
+ this.kernel.store.set("home", config.home)
1990
1984
  }
1991
1985
 
1992
1986
  }
@@ -2015,7 +2009,6 @@ class Server {
2015
2009
 
2016
2010
  home = this.kernel.store.get("home")
2017
2011
  theme = this.kernel.store.get("theme")
2018
- let new_home = this.kernel.store.get("new_home")
2019
2012
  }
2020
2013
  async startLogging(homedir) {
2021
2014
  console.log(">>>>>>> startLogging", homedir)
@@ -2052,7 +2045,27 @@ class Server {
2052
2045
  }
2053
2046
  }
2054
2047
  }
2048
+ async running(port) {
2049
+ let p = port || DEFAULT_PORT
2050
+ const available = await Util.is_port_available(p)
2051
+ if (available) {
2052
+ return false
2053
+ } else {
2054
+ return true
2055
+ }
2056
+ }
2055
2057
  async start(options) {
2058
+ console.time("###### ALL")
2059
+
2060
+ // // ONLY start if not already running
2061
+ // let is_running = await this.running(this.port || DEFAULT_PORT)
2062
+ // if (is_running) {
2063
+ // console.log("pinokiod already running, skip..")
2064
+ // return
2065
+ // }
2066
+ //
2067
+
2068
+ console.time("######### 1")
2056
2069
  this.debug = false
2057
2070
  if (options) {
2058
2071
  this.debug = options.debug
@@ -2084,10 +2097,14 @@ class Server {
2084
2097
  // } catch (e) {
2085
2098
  // }
2086
2099
  }
2100
+ console.timeEnd("######### 1")
2087
2101
 
2088
2102
  // configure from kernel.store
2103
+ console.time("###### 2 syncConfig")
2089
2104
  await this.syncConfig()
2105
+ console.timeEnd("###### 2 syncConfig")
2090
2106
 
2107
+ console.time("###### 3 startLogging")
2091
2108
  try {
2092
2109
  let _home = this.kernel.store.get("home")
2093
2110
  console.log({ _home })
@@ -2097,11 +2114,12 @@ class Server {
2097
2114
  } catch (e) {
2098
2115
  console.log("start logging attempt", e)
2099
2116
  }
2117
+ console.timeEnd("###### 3 startLogging")
2100
2118
 
2101
2119
  // determine port if port is not passed in
2102
2120
 
2103
2121
  if (!this.port) {
2104
- this.port = 42000
2122
+ this.port = DEFAULT_PORT
2105
2123
  // let platform = os.platform()
2106
2124
  // if (platform === 'linux') {
2107
2125
  // // on linux you are not allowed to listen on ports below 1024
@@ -2151,15 +2169,19 @@ class Server {
2151
2169
  console.log("[DONE] Updating to the new version")
2152
2170
  }
2153
2171
  }
2154
-
2172
+ console.time("###### 4 kernel.init")
2155
2173
  // initialize kernel
2156
2174
  await this.kernel.init({ port: this.port})
2175
+ console.timeEnd("###### 4 kernel.init")
2157
2176
 
2158
2177
  console.log({ needInitHome })
2178
+ console.time("###### 5 initHome")
2159
2179
  if (needInitHome) {
2160
2180
  await this.kernel.initHome()
2161
2181
  }
2182
+ console.timeEnd("###### 5 initHome")
2162
2183
 
2184
+ console.time("###### 6")
2163
2185
  if (this.kernel.homedir) {
2164
2186
  let ex = await this.kernel.exists(this.kernel.homedir, "ENVIRONMENT")
2165
2187
  if (!ex) {
@@ -2167,6 +2189,7 @@ class Server {
2167
2189
  await fs.promises.writeFile(path.resolve(this.kernel.homedir, "ENVIRONMENT"), str)
2168
2190
  }
2169
2191
  }
2192
+ console.timeEnd("###### 6")
2170
2193
 
2171
2194
 
2172
2195
 
@@ -2251,6 +2274,7 @@ class Server {
2251
2274
 
2252
2275
 
2253
2276
  //await this.configure()
2277
+ console.time("###### 7")
2254
2278
 
2255
2279
  this.started = false
2256
2280
  this.app = express();
@@ -2497,6 +2521,20 @@ class Server {
2497
2521
  let str = await fs.promises.readFile(req.query.logpath, "utf8")
2498
2522
  res.send(str)
2499
2523
  }))
2524
+ this.app.post("/mkdir", ex(async (req, res) => {
2525
+ let folder = req.body.folder
2526
+ let folder_path = path.resolve(this.kernel.api.userdir, req.body.folder)
2527
+ try {
2528
+ await fs.promises.mkdir(folder_path)
2529
+ res.json({
2530
+ success: "/pinokio/browser/"+folder
2531
+ })
2532
+ } catch (e) {
2533
+ res.json({
2534
+ error: e.message
2535
+ })
2536
+ }
2537
+ }))
2500
2538
  this.app.post("/proxy", ex(async (req, res) => {
2501
2539
  /*
2502
2540
  req.body := {
@@ -2634,6 +2672,8 @@ class Server {
2634
2672
  }
2635
2673
  if (config.icon) {
2636
2674
  config.icon = `/${env_path}/${config.icon}?raw=true`
2675
+ } else {
2676
+ config.icon = "/pinokio-black.png"
2637
2677
  }
2638
2678
 
2639
2679
  let name
@@ -2694,6 +2734,8 @@ class Server {
2694
2734
  config.pre.forEach((item) => {
2695
2735
  if (item.icon) {
2696
2736
  item.icon = `/api/${req.params.name}/${item.icon}?raw=true`
2737
+ } else {
2738
+ item.icon = "/pinokio-black.png"
2697
2739
  }
2698
2740
  if (!item.href.startsWith("http")) {
2699
2741
  item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
@@ -2802,6 +2844,43 @@ class Server {
2802
2844
  }
2803
2845
  res.json({ config: this.xterm })
2804
2846
  }))
2847
+ // this.app.get("/shell", ex(async (req, res) => {
2848
+ // console.log("req.query", req.query)
2849
+ // res.render("terminal", {
2850
+ // memory: [],
2851
+ // logo: this.logo,
2852
+ // theme: this.theme,
2853
+ // run: true,
2854
+ // stop: false,
2855
+ // runnable: true,
2856
+ // agent: this.agent,
2857
+ //// uri,
2858
+ // mod: true,
2859
+ // json: true,
2860
+ // js: false,
2861
+ // install_required: false,
2862
+ // current: req.originalUrl,
2863
+ // })
2864
+ // }))
2865
+ this.app.get("/shells/:name", ex(async (req, res) => {
2866
+ let p = this.kernel.path("api", req.params.name)
2867
+ let venv_folders = await Util.find_venv(p)
2868
+ console.log({ venv_folders })
2869
+ let items = venv_folders.map((f) => {
2870
+ let args = {
2871
+ message: "",
2872
+ venv: f
2873
+ }
2874
+ return {
2875
+ name: f,
2876
+ cmd: new URLSearchParams(args).toString()
2877
+ }
2878
+ })
2879
+ console.log({ items })
2880
+ res.json({
2881
+ items
2882
+ })
2883
+ }))
2805
2884
  this.app.get("/du/:name", ex(async (req, res) => {
2806
2885
  let p = this.kernel.path("api", req.params.name)
2807
2886
  let d1 = await Util.du(p)
@@ -3421,6 +3500,8 @@ class Server {
3421
3500
  // install
3422
3501
  this.server = httpserver.createServer(this.app);
3423
3502
  this.socket = new Socket(this)
3503
+ console.timeEnd("###### 7")
3504
+ console.time("###### 8")
3424
3505
  await new Promise((resolve, reject) => {
3425
3506
  this.listening = this.server.listen(this.port, () => {
3426
3507
  console.log(`Server listening on port ${this.port}`)
@@ -3430,6 +3511,8 @@ class Server {
3430
3511
  server: this.listening
3431
3512
  });
3432
3513
  })
3514
+ console.timeEnd("###### 8")
3515
+ console.timeEnd("###### ALL")
3433
3516
 
3434
3517
  }
3435
3518
  }
Binary file
Binary file
@@ -1701,6 +1701,11 @@ footer.status {
1701
1701
  text-transform: uppercase;
1702
1702
  }
1703
1703
 
1704
+ #fsmenu {
1705
+ background: rgba(0,0,100,0.04);
1706
+ display: flex;
1707
+ padding: 20px 20px 0;
1708
+ }
1704
1709
  mark{
1705
1710
  background: firebrick;
1706
1711
  color: white;