18 Commits

Author SHA1 Message Date
51fcccafa2 fix os type calculation and bump version 2023-09-11 16:18:05 -07:00
e3913ab4c9 add todo list 2023-09-11 16:11:06 -07:00
c16f21bba3 Merge branch 'terminal' 2023-09-11 16:10:58 -07:00
61d9acc7c6 request unlock/credentials when terminal is launched from locked/empty state 2023-09-11 16:00:58 -07:00
8d7b01629d make keybinds configurable 2023-09-10 14:04:09 -07:00
5685948608 add hotkeys to show window and launch terminal 2023-09-09 07:29:57 -07:00
c98a065587 make terminal emulator configurable 2023-09-09 06:30:19 -07:00
e46c3d2b4d tweak home screen 2023-09-05 06:12:26 -07:00
fa228acc3a use svg animation for spinner 2023-08-06 21:25:24 -07:00
e7e0f9d33e very basic launch button 2023-08-03 22:08:24 -07:00
a51b20add7 combine ExecError with LaunchError and use Session::try_get() instead of matching 2023-08-03 21:57:55 -07:00
890f715388 usable backend for terminal launch 2023-08-03 16:35:15 -07:00
89bc74e644 start working on terminal launcher 2023-08-02 19:57:37 -07:00
60c24e3ee4 don't autohide on first launch 2023-07-11 16:13:20 -07:00
486001b584 improve display of GetSessionError 2023-07-11 14:34:54 -07:00
52c949e396 v0.2.3 2023-07-11 10:35:56 -07:00
d7c5c2f37b update dependencies 2023-07-11 09:52:13 -07:00
ae5b8f31db remove spinner when unlock fails 2023-07-11 09:50:35 -07:00
31 changed files with 1665 additions and 893 deletions

540
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "creddy",
"version": "0.1.0",
"version": "0.2.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "creddy",
"version": "0.1.0",
"version": "0.2.2",
"dependencies": {
"@tauri-apps/api": "^1.0.2",
"daisyui": "^2.51.5"
@ -21,6 +21,17 @@
"vite": "^3.0.7"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
@ -155,9 +166,9 @@
}
},
"node_modules/@tauri-apps/api": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz",
"integrity": "sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.4.0.tgz",
"integrity": "sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==",
"engines": {
"node": ">= 14.6.0",
"npm": ">= 6.6.0",
@ -169,9 +180,9 @@
}
},
"node_modules/@tauri-apps/cli": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.2.3.tgz",
"integrity": "sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.4.0.tgz",
"integrity": "sha512-VXYr2i2iVFl98etQSQsqLzXgX96bnWiNZd1YADgatqwy/qecbd6Kl5ZAPB5R4ynsgE8A1gU7Fbzh7dCEQYFfmA==",
"dev": true,
"bin": {
"tauri": "tauri.js"
@ -184,21 +195,22 @@
"url": "https://opencollective.com/tauri"
},
"optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "1.2.3",
"@tauri-apps/cli-darwin-x64": "1.2.3",
"@tauri-apps/cli-linux-arm-gnueabihf": "1.2.3",
"@tauri-apps/cli-linux-arm64-gnu": "1.2.3",
"@tauri-apps/cli-linux-arm64-musl": "1.2.3",
"@tauri-apps/cli-linux-x64-gnu": "1.2.3",
"@tauri-apps/cli-linux-x64-musl": "1.2.3",
"@tauri-apps/cli-win32-ia32-msvc": "1.2.3",
"@tauri-apps/cli-win32-x64-msvc": "1.2.3"
"@tauri-apps/cli-darwin-arm64": "1.4.0",
"@tauri-apps/cli-darwin-x64": "1.4.0",
"@tauri-apps/cli-linux-arm-gnueabihf": "1.4.0",
"@tauri-apps/cli-linux-arm64-gnu": "1.4.0",
"@tauri-apps/cli-linux-arm64-musl": "1.4.0",
"@tauri-apps/cli-linux-x64-gnu": "1.4.0",
"@tauri-apps/cli-linux-x64-musl": "1.4.0",
"@tauri-apps/cli-win32-arm64-msvc": "1.4.0",
"@tauri-apps/cli-win32-ia32-msvc": "1.4.0",
"@tauri-apps/cli-win32-x64-msvc": "1.4.0"
}
},
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.2.3.tgz",
"integrity": "sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.4.0.tgz",
"integrity": "sha512-nA/ml0SfUt6/CYLVbHmT500Y+ijqsuv5+s9EBnVXYSLVg9kbPUZJJHluEYK+xKuOj6xzyuT/+rZFMRapmJD3jQ==",
"cpu": [
"arm64"
],
@ -212,9 +224,9 @@
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.2.3.tgz",
"integrity": "sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.4.0.tgz",
"integrity": "sha512-ov/F6Zr+dg9B0PtRu65stFo2G0ow2TUlneqYYrkj+vA3n+moWDHfVty0raDjMLQbQt3rv3uayFMXGPMgble9OA==",
"cpu": [
"x64"
],
@ -228,9 +240,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.2.3.tgz",
"integrity": "sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.4.0.tgz",
"integrity": "sha512-zwjbiMncycXDV7doovymyKD7sCg53ouAmfgpUqEBOTY3vgBi9TwijyPhJOqoG5vUVWhouNBC08akGmE4dja15g==",
"cpu": [
"arm"
],
@ -244,9 +256,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.2.3.tgz",
"integrity": "sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.4.0.tgz",
"integrity": "sha512-5MCBcziqXC72mMXnkZU68mutXIR6zavDxopArE2gQtK841IlE06bIgtLi0kUUhlFJk2nhPRgiDgdLbrPlyt7fw==",
"cpu": [
"arm64"
],
@ -260,9 +272,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.2.3.tgz",
"integrity": "sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.4.0.tgz",
"integrity": "sha512-7J3pRB6n6uNYgIfCeKt2Oz8J7oSaz2s8GGFRRH2HPxuTHrBNCinzVYm68UhVpJrL3bnGkU0ziVZLsW/iaOGfUg==",
"cpu": [
"arm64"
],
@ -276,9 +288,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.2.3.tgz",
"integrity": "sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.4.0.tgz",
"integrity": "sha512-Zh5gfAJxOv5AVWxcwuueaQ2vIAhlg0d6nZui6nMyfIJ8dbf3aZQ5ZzP38sYow5h/fbvgL+3GSQxZRBIa3c2E1w==",
"cpu": [
"x64"
],
@ -292,9 +304,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.2.3.tgz",
"integrity": "sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.4.0.tgz",
"integrity": "sha512-OLAYoICU3FaYiTdBsI+lQTKnDHeMmFMXIApN0M+xGiOkoIOQcV9CConMPjgmJQ867+NHRNgUGlvBEAh9CiJodQ==",
"cpu": [
"x64"
],
@ -307,10 +319,26 @@
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.4.0.tgz",
"integrity": "sha512-gZ05GENFbI6CB5MlOUsLlU0kZ9UtHn9riYtSXKT6MYs8HSPRffPHaHSL0WxsJweWh9nR5Hgh/TUU8uW3sYCzCg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.2.3.tgz",
"integrity": "sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.4.0.tgz",
"integrity": "sha512-JsetT/lTx/Zq98eo8T5CiRyF1nKeX04RO8JlJrI3ZOYsZpp/A5RJvMd/szQ17iOzwiHdge+tx7k2jHysR6oBlQ==",
"cpu": [
"ia32"
],
@ -324,9 +352,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.2.3.tgz",
"integrity": "sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.4.0.tgz",
"integrity": "sha512-z8Olcnwp5aYhzqUAarFjqF+oELCjuYWnB2HAJHlfsYNfDCAORY5kct3Fklz8PSsubC3U2EugWn8n42DwnThurg==",
"cpu": [
"x64"
],
@ -427,9 +455,9 @@
}
},
"node_modules/browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
"version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"funding": [
{
"type": "opencollective",
@ -438,13 +466,17 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001449",
"electron-to-chromium": "^1.4.284",
"node-releases": "^2.0.8",
"update-browserslist-db": "^1.0.10"
"caniuse-lite": "^1.0.30001503",
"electron-to-chromium": "^1.4.431",
"node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.11"
},
"bin": {
"browserslist": "cli.js"
@ -462,9 +494,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001481",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
"integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==",
"version": "1.0.30001515",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz",
"integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==",
"funding": [
{
"type": "opencollective",
@ -588,9 +620,9 @@
}
},
"node_modules/daisyui": {
"version": "2.51.5",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.51.5.tgz",
"integrity": "sha512-L05dRw0tasmz2Ha+10LhftEGLq4kaA8vRR/T0wDaXfHwqcgsf81jfXDJ6NlZ63Z7Rl1k3rj7UHs0l0p7CM3aYA==",
"version": "2.52.0",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.52.0.tgz",
"integrity": "sha512-LQTA5/IVXAJHBMFoeaEMfd7/akAFPPcdQPR3O9fzzcFiczneJFM73CFPnScmW2sOgn/D83cvkP854ep2T9OfTg==",
"dependencies": {
"color": "^4.2",
"css-selector-tokenizer": "^0.8.0",
@ -643,9 +675,9 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.369",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz",
"integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg=="
"version": "1.4.455",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz",
"integrity": "sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA=="
},
"node_modules/esbuild": {
"version": "0.15.18",
@ -1013,9 +1045,9 @@
}
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -1169,9 +1201,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
"integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dependencies": {
"has": "^1.0.3"
},
@ -1207,9 +1239,9 @@
}
},
"node_modules/jiti": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
"integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
"bin": {
"jiti": "bin/jiti.js"
}
@ -1313,9 +1345,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
@ -1395,17 +1427,17 @@
}
},
"node_modules/pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"engines": {
"node": ">= 6"
}
},
"node_modules/postcss": {
"version": "8.4.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
"version": "8.4.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
"integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==",
"funding": [
{
"type": "opencollective",
@ -1430,16 +1462,16 @@
}
},
"node_modules/postcss-import": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
"integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
},
"engines": {
"node": ">=10.0.0"
"node": ">=14.0.0"
},
"peerDependencies": {
"postcss": "^8.0.0"
@ -1464,15 +1496,15 @@
}
},
"node_modules/postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
"integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
"yaml": "^2.1.1"
},
"engines": {
"node": ">= 10"
"node": ">= 14"
},
"funding": {
"type": "opencollective",
@ -1492,11 +1524,11 @@
}
},
"node_modules/postcss-nested": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
"integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
"dependencies": {
"postcss-selector-parser": "^6.0.10"
"postcss-selector-parser": "^6.0.11"
},
"engines": {
"node": ">=12.0"
@ -1510,9 +1542,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -1545,17 +1577,6 @@
}
]
},
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -1693,65 +1714,61 @@
}
},
"node_modules/svelte": {
"version": "3.58.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz",
"integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==",
"version": "3.59.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/svelte-hmr": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz",
"integrity": "sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.2.tgz",
"integrity": "sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==",
"dev": true,
"engines": {
"node": "^12.20 || ^14.13.1 || >= 16"
},
"peerDependencies": {
"svelte": ">=3.19.0"
"svelte": "^3.19.0 || ^4.0.0-next.0"
}
},
"node_modules/tailwindcss": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
"integrity": "sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"color-name": "^1.1.4",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.17.2",
"lilconfig": "^2.0.6",
"jiti": "^1.18.2",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
"postcss": "^8.0.9",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
"postcss-nested": "6.0.0",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"quick-lru": "^5.1.1",
"resolve": "^1.22.1",
"sucrase": "^3.29.0"
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=12.13.0"
},
"peerDependencies": {
"postcss": "^8.0.9"
"node": ">=14.0.0"
}
},
"node_modules/thenify": {
@ -1824,9 +1841,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/vite": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.6.tgz",
"integrity": "sha512-nTXTxYVvaQNLoW5BQ8PNNQ3lPia57gzsQU/Khv+JvzKPku8kNZL6NMUR/qwXhMG6E+g1idqEPanomJ+VZgixEg==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
@ -1892,15 +1909,20 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"engines": {
"node": ">= 6"
"node": ">= 14"
}
}
},
"dependencies": {
"@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="
},
"@esbuild/android-arm": {
"version": "0.15.18",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
@ -1994,87 +2016,95 @@
}
},
"@tauri-apps/api": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.2.0.tgz",
"integrity": "sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw=="
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.4.0.tgz",
"integrity": "sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw=="
},
"@tauri-apps/cli": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.2.3.tgz",
"integrity": "sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.4.0.tgz",
"integrity": "sha512-VXYr2i2iVFl98etQSQsqLzXgX96bnWiNZd1YADgatqwy/qecbd6Kl5ZAPB5R4ynsgE8A1gU7Fbzh7dCEQYFfmA==",
"dev": true,
"requires": {
"@tauri-apps/cli-darwin-arm64": "1.2.3",
"@tauri-apps/cli-darwin-x64": "1.2.3",
"@tauri-apps/cli-linux-arm-gnueabihf": "1.2.3",
"@tauri-apps/cli-linux-arm64-gnu": "1.2.3",
"@tauri-apps/cli-linux-arm64-musl": "1.2.3",
"@tauri-apps/cli-linux-x64-gnu": "1.2.3",
"@tauri-apps/cli-linux-x64-musl": "1.2.3",
"@tauri-apps/cli-win32-ia32-msvc": "1.2.3",
"@tauri-apps/cli-win32-x64-msvc": "1.2.3"
"@tauri-apps/cli-darwin-arm64": "1.4.0",
"@tauri-apps/cli-darwin-x64": "1.4.0",
"@tauri-apps/cli-linux-arm-gnueabihf": "1.4.0",
"@tauri-apps/cli-linux-arm64-gnu": "1.4.0",
"@tauri-apps/cli-linux-arm64-musl": "1.4.0",
"@tauri-apps/cli-linux-x64-gnu": "1.4.0",
"@tauri-apps/cli-linux-x64-musl": "1.4.0",
"@tauri-apps/cli-win32-arm64-msvc": "1.4.0",
"@tauri-apps/cli-win32-ia32-msvc": "1.4.0",
"@tauri-apps/cli-win32-x64-msvc": "1.4.0"
}
},
"@tauri-apps/cli-darwin-arm64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.2.3.tgz",
"integrity": "sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.4.0.tgz",
"integrity": "sha512-nA/ml0SfUt6/CYLVbHmT500Y+ijqsuv5+s9EBnVXYSLVg9kbPUZJJHluEYK+xKuOj6xzyuT/+rZFMRapmJD3jQ==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-darwin-x64": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.2.3.tgz",
"integrity": "sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.4.0.tgz",
"integrity": "sha512-ov/F6Zr+dg9B0PtRu65stFo2G0ow2TUlneqYYrkj+vA3n+moWDHfVty0raDjMLQbQt3rv3uayFMXGPMgble9OA==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.2.3.tgz",
"integrity": "sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.4.0.tgz",
"integrity": "sha512-zwjbiMncycXDV7doovymyKD7sCg53ouAmfgpUqEBOTY3vgBi9TwijyPhJOqoG5vUVWhouNBC08akGmE4dja15g==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-linux-arm64-gnu": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.2.3.tgz",
"integrity": "sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.4.0.tgz",
"integrity": "sha512-5MCBcziqXC72mMXnkZU68mutXIR6zavDxopArE2gQtK841IlE06bIgtLi0kUUhlFJk2nhPRgiDgdLbrPlyt7fw==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-linux-arm64-musl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.2.3.tgz",
"integrity": "sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.4.0.tgz",
"integrity": "sha512-7J3pRB6n6uNYgIfCeKt2Oz8J7oSaz2s8GGFRRH2HPxuTHrBNCinzVYm68UhVpJrL3bnGkU0ziVZLsW/iaOGfUg==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-linux-x64-gnu": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.2.3.tgz",
"integrity": "sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.4.0.tgz",
"integrity": "sha512-Zh5gfAJxOv5AVWxcwuueaQ2vIAhlg0d6nZui6nMyfIJ8dbf3aZQ5ZzP38sYow5h/fbvgL+3GSQxZRBIa3c2E1w==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-linux-x64-musl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.2.3.tgz",
"integrity": "sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.4.0.tgz",
"integrity": "sha512-OLAYoICU3FaYiTdBsI+lQTKnDHeMmFMXIApN0M+xGiOkoIOQcV9CConMPjgmJQ867+NHRNgUGlvBEAh9CiJodQ==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-win32-arm64-msvc": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.4.0.tgz",
"integrity": "sha512-gZ05GENFbI6CB5MlOUsLlU0kZ9UtHn9riYtSXKT6MYs8HSPRffPHaHSL0WxsJweWh9nR5Hgh/TUU8uW3sYCzCg==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-win32-ia32-msvc": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.2.3.tgz",
"integrity": "sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.4.0.tgz",
"integrity": "sha512-JsetT/lTx/Zq98eo8T5CiRyF1nKeX04RO8JlJrI3ZOYsZpp/A5RJvMd/szQ17iOzwiHdge+tx7k2jHysR6oBlQ==",
"dev": true,
"optional": true
},
"@tauri-apps/cli-win32-x64-msvc": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.2.3.tgz",
"integrity": "sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.4.0.tgz",
"integrity": "sha512-z8Olcnwp5aYhzqUAarFjqF+oELCjuYWnB2HAJHlfsYNfDCAORY5kct3Fklz8PSsubC3U2EugWn8n42DwnThurg==",
"dev": true,
"optional": true
},
@ -2138,14 +2168,14 @@
}
},
"browserslist": {
"version": "4.21.5",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
"version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"requires": {
"caniuse-lite": "^1.0.30001449",
"electron-to-chromium": "^1.4.284",
"node-releases": "^2.0.8",
"update-browserslist-db": "^1.0.10"
"caniuse-lite": "^1.0.30001503",
"electron-to-chromium": "^1.4.431",
"node-releases": "^2.0.12",
"update-browserslist-db": "^1.0.11"
}
},
"camelcase-css": {
@ -2154,9 +2184,9 @@
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
"caniuse-lite": {
"version": "1.0.30001481",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz",
"integrity": "sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ=="
"version": "1.0.30001515",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz",
"integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA=="
},
"chokidar": {
"version": "3.5.3",
@ -2239,9 +2269,9 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
"daisyui": {
"version": "2.51.5",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.51.5.tgz",
"integrity": "sha512-L05dRw0tasmz2Ha+10LhftEGLq4kaA8vRR/T0wDaXfHwqcgsf81jfXDJ6NlZ63Z7Rl1k3rj7UHs0l0p7CM3aYA==",
"version": "2.52.0",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.52.0.tgz",
"integrity": "sha512-LQTA5/IVXAJHBMFoeaEMfd7/akAFPPcdQPR3O9fzzcFiczneJFM73CFPnScmW2sOgn/D83cvkP854ep2T9OfTg==",
"requires": {
"color": "^4.2",
"css-selector-tokenizer": "^0.8.0",
@ -2275,9 +2305,9 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"electron-to-chromium": {
"version": "1.4.369",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.369.tgz",
"integrity": "sha512-LfxbHXdA/S+qyoTEA4EbhxGjrxx7WK2h6yb5K2v0UCOufUKX+VZaHbl3svlzZfv9sGseym/g3Ne4DpsgRULmqg=="
"version": "1.4.455",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz",
"integrity": "sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA=="
},
"esbuild": {
"version": "0.15.18",
@ -2455,9 +2485,9 @@
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
},
"fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -2575,9 +2605,9 @@
}
},
"is-core-module": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
"integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"requires": {
"has": "^1.0.3"
}
@ -2601,9 +2631,9 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"jiti": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg=="
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
"integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg=="
},
"kleur": {
"version": "4.1.5",
@ -2674,9 +2704,9 @@
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
},
"node-releases": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w=="
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
},
"normalize-path": {
"version": "3.0.0",
@ -2732,14 +2762,14 @@
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
},
"pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ=="
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
},
"postcss": {
"version": "8.4.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
"version": "8.4.25",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
"integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==",
"requires": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
@ -2747,9 +2777,9 @@
}
},
"postcss-import": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz",
"integrity": "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==",
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"requires": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
@ -2765,26 +2795,26 @@
}
},
"postcss-load-config": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
"integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
"integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
"requires": {
"lilconfig": "^2.0.5",
"yaml": "^1.10.2"
"yaml": "^2.1.1"
}
},
"postcss-nested": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
"integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
"requires": {
"postcss-selector-parser": "^6.0.10"
"postcss-selector-parser": "^6.0.11"
}
},
"postcss-selector-parser": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -2800,11 +2830,6 @@
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -2892,47 +2917,46 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"svelte": {
"version": "3.58.0",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.58.0.tgz",
"integrity": "sha512-brIBNNB76mXFmU/Kerm4wFnkskBbluBDCjx/8TcpYRb298Yh2dztS2kQ6bhtjMcvUhd5ynClfwpz5h2gnzdQ1A==",
"version": "3.59.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
"dev": true
},
"svelte-hmr": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.1.tgz",
"integrity": "sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.2.tgz",
"integrity": "sha512-q/bAruCvFLwvNbeE1x3n37TYFb3mTBJ6TrCq6p2CoFbSTNhDE9oAtEfpy+wmc9So8AG0Tja+X0/mJzX9tSfvIg==",
"dev": true,
"requires": {}
},
"tailwindcss": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz",
"integrity": "sha512-Vkiouc41d4CEq0ujXl6oiGFQ7bA3WEhUZdTgXAhtKxSy49OmKs8rEfQmupsfF0IGW8fv2iQkp1EVUuapCFrZ9g==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
"requires": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"color-name": "^1.1.4",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.17.2",
"lilconfig": "^2.0.6",
"jiti": "^1.18.2",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
"postcss": "^8.0.9",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
"postcss-nested": "6.0.0",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"quick-lru": "^5.1.1",
"resolve": "^1.22.1",
"sucrase": "^3.29.0"
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
}
},
"thenify": {
@ -2979,9 +3003,9 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"vite": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.6.tgz",
"integrity": "sha512-nTXTxYVvaQNLoW5BQ8PNNQ3lPia57gzsQU/Khv+JvzKPku8kNZL6NMUR/qwXhMG6E+g1idqEPanomJ+VZgixEg==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
"dev": true,
"requires": {
"esbuild": "^0.15.9",
@ -3004,9 +3028,9 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ=="
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "creddy",
"version": "0.2.2",
"version": "0.3.0",
"scripts": {
"dev": "vite",
"build": "vite build",

1083
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "creddy"
version = "0.2.2"
version = "0.3.0"
description = "A friendly AWS credentials manager"
authors = ["Joseph Montanaro"]
license = ""
@ -25,7 +25,7 @@ tauri-build = { version = "1.0.4", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = ["dialog", "os-all", "system-tray"] }
tauri = { version = "1.2", features = ["dialog", "dialog-open", "global-shortcut", "os-all", "system-tray"] }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "dev" }
sodiumoxide = "0.2.7"
tokio = { version = ">=1.19", features = ["full"] }
@ -46,6 +46,7 @@ clap = { version = "3.2.23", features = ["derive"] }
is-terminal = "0.4.7"
argon2 = { version = "0.5.0", features = ["std"] }
chacha20poly1305 = { version = "0.10.1", features = ["std"] }
which = "4.4.0"
[features]
# by default Tauri runs in production mode

View File

@ -42,6 +42,7 @@ pub fn run() -> tauri::Result<()> {
ipc::save_credentials,
ipc::get_config,
ipc::save_config,
ipc::launch_terminal,
])
.setup(|app| rt::block_on(setup(app)))
.build(tauri::generate_context!())?
@ -74,13 +75,17 @@ pub async fn connect_db() -> Result<SqlitePool, SetupError> {
async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
APP.set(app.handle()).unwrap();
let is_first_launch = config::get_or_create_db_path()?.exists();
let pool = connect_db().await?;
let conf = AppConfig::load(&pool).await?;
let session = Session::load(&pool).await?;
let srv = Server::new(conf.listen_addr, conf.listen_port, app.handle()).await?;
config::set_auto_launch(conf.start_on_login)?;
if !conf.start_minimized {
config::register_hotkeys(&conf.hotkeys)?;
// if session is empty, this is probably the first launch, so don't autohide
if !conf.start_minimized || is_first_launch {
app.get_window("main")
.ok_or(HandlerError::NoMainWindow)?
.show()?;

View File

@ -1,3 +1,4 @@
use std::ffi::OsString;
use std::process::Command as ChildCommand;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
@ -90,15 +91,28 @@ pub fn exec(args: &ArgMatches) -> Result<(), CliError> {
#[cfg(unix)]
{
let e = cmd.exec(); // never returns if successful
Err(ExecError::ExecutionFailed(e))?;
Ok(())
// cmd.exec() never returns if successful
let e = cmd.exec();
match e.kind() {
std::io::ErrorKind::NotFound => {
let name: OsString = cmd_name.into();
Err(ExecError::NotFound(name).into())
}
e => Err(ExecError::ExecutionFailed(e).into()),
}
}
#[cfg(windows)]
{
let mut child = cmd.spawn()
.map_err(|e| ExecError::ExecutionFailed(e))?;
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let name: OsString = cmd_name.into();
return Err(ExecError::NotFound(name).into());
}
Err(e) => return Err(ExecError::ExecutionFailed(e).into()),
};
let status = child.wait()
.map_err(|e| ExecError::ExecutionFailed(e))?;
std::process::exit(status.code().unwrap_or(1));

View File

@ -5,10 +5,41 @@ use auto_launch::AutoLaunchBuilder;
use is_terminal::IsTerminal;
use serde::{Serialize, Deserialize};
use sqlx::SqlitePool;
use tauri::{
Manager,
GlobalShortcutManager,
async_runtime as rt,
};
use crate::errors::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TermConfig {
pub name: String,
// we call it exec because it isn't always the actual path,
// in some cases it's just the name and relies on path-searching
// it's a string because it can come from the frontend as json
pub exec: String,
pub args: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Hotkey {
pub keys: String,
pub enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct HotkeysConfig {
// tauri uses strings to represent keybinds, so we will as well
pub show_window: Hotkey,
pub launch_terminal: Hotkey,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppConfig {
#[serde(default = "default_listen_addr")]
@ -21,6 +52,10 @@ pub struct AppConfig {
pub start_minimized: bool,
#[serde(default = "default_start_on_login")]
pub start_on_login: bool,
#[serde(default = "default_term_config")]
pub terminal: TermConfig,
#[serde(default = "default_hotkey_config")]
pub hotkeys: HotkeysConfig,
}
@ -32,6 +67,8 @@ impl Default for AppConfig {
rehide_ms: default_rehide_ms(),
start_minimized: default_start_minimized(),
start_on_login: default_start_on_login(),
terminal: default_term_config(),
hotkeys: default_hotkey_config(),
}
}
}
@ -116,6 +153,91 @@ fn default_listen_port() -> u16 {
}
}
fn default_term_config() -> TermConfig {
#[cfg(windows)]
{
let shell = if which::which("pwsh.exe").is_ok() {
"pwsh.exe".to_string()
}
else {
"powershell.exe".to_string()
};
let (exec, args) = if cfg!(debug_assertions) {
("conhost.exe".to_string(), vec![shell.clone()])
} else {
(shell.clone(), vec![])
};
TermConfig { name: shell, exec, args }
}
#[cfg(unix)]
{
for bin in ["gnome-terminal", "konsole"] {
if let Ok(_) = which::which(bin) {
return TermConfig {
name: bin.into(),
exec: bin.into(),
args: vec![],
}
}
}
return TermConfig {
name: "gnome-terminal".into(),
exec: "gnome-terminal".into(),
args: vec![],
};
}
}
fn default_hotkey_config() -> HotkeysConfig {
HotkeysConfig {
show_window: Hotkey {keys: "alt+shift+C".into(), enabled: true},
launch_terminal: Hotkey {keys: "alt+shift+T".into(), enabled: true},
}
}
// note: will panic if called before APP is set
pub fn register_hotkeys(hotkeys: &HotkeysConfig) -> tauri::Result<()> {
let app = crate::app::APP.get().unwrap();
let mut manager = app.global_shortcut_manager();
manager.unregister_all()?;
if hotkeys.show_window.enabled {
let handle = app.app_handle();
manager.register(
&hotkeys.show_window.keys,
move || {
handle.get_window("main")
.map(|w| w.show().error_popup("Failed to show"))
.ok_or(HandlerError::NoMainWindow)
.error_popup("No main window");
},
)?;
}
if hotkeys.launch_terminal.enabled {
// register() doesn't take an async fn, so we have to use spawn
manager.register(
&hotkeys.launch_terminal.keys,
|| {
rt::spawn(async {
crate::terminal::launch(false)
.await
.error_popup("Failed to launch");
});
}
)?;
}
Ok(())
}
fn default_listen_addr() -> Ipv4Addr { Ipv4Addr::LOCALHOST }
fn default_rehide_ms() -> u64 { 1000 }
// start minimized and on login only in production mode

View File

@ -81,6 +81,16 @@ impl Session {
Session::Empty => Err(GetSessionError::CredentialsEmpty),
}
}
pub fn try_get(
&self
) -> Result<(&BaseCredentials, &SessionCredentials), GetCredentialsError> {
match self {
Self::Empty => Err(GetCredentialsError::Empty),
Self::Locked(_) => Err(GetCredentialsError::Locked),
Self::Unlocked{ ref base, ref session } => Ok((base, session))
}
}
}

View File

@ -1,5 +1,6 @@
use std::error::Error;
use std::convert::AsRef;
use std::ffi::OsString;
use std::sync::mpsc;
use strum_macros::AsRefStr;
@ -57,8 +58,12 @@ where
E: Error,
M: serde::ser::SerializeMap,
{
let src = err.source().map(|s| format!("{s}"));
map.serialize_entry("source", &src)
let msg = err.source().map(|s| format!("{s}"));
map.serialize_entry("msg", &msg)?;
map.serialize_entry("code", &None::<&str>)?;
map.serialize_entry("source", &None::<&str>)?;
Ok(())
}
@ -90,6 +95,8 @@ pub enum SetupError {
ServerSetupError(#[from] std::io::Error),
#[error("Failed to resolve data directory: {0}")]
DataDir(#[from] DataDirError),
#[error("Failed to register hotkeys: {0}")]
RegisterHotkeys(#[from] tauri::Error),
}
@ -212,16 +219,6 @@ pub enum RequestError {
}
// Errors encountered while running a subprocess (via creddy exec)
#[derive(Debug, ThisError, AsRefStr)]
pub enum ExecError {
#[error("Please specify a command")]
NoCommand,
#[error("Failed to execute command: {0}")]
ExecutionFailed(#[from] std::io::Error)
}
#[derive(Debug, ThisError, AsRefStr)]
pub enum CliError {
#[error(transparent)]
@ -233,6 +230,33 @@ pub enum CliError {
}
// Errors encountered while trying to launch a child process
#[derive(Debug, ThisError, AsRefStr)]
pub enum ExecError {
#[error("Please specify a command")]
NoCommand,
#[error("Executable not found: {0:?}")]
NotFound(OsString),
#[error("Failed to execute command: {0}")]
ExecutionFailed(#[from] std::io::Error),
#[error(transparent)]
GetCredentials(#[from] GetCredentialsError),
}
#[derive(Debug, ThisError, AsRefStr)]
pub enum LaunchTerminalError {
#[error("Could not discover main window")]
NoMainWindow,
#[error("Failed to communicate with main Creddy window")]
IpcFailed(#[from] tauri::Error),
#[error("Failed to launch terminal: {0}")]
Exec(#[from] ExecError),
#[error(transparent)]
GetCredentials(#[from] GetCredentialsError),
}
// =========================
// Serialize implementations
// =========================
@ -323,3 +347,33 @@ impl Serialize for UnlockError {
map.end()
}
}
impl Serialize for ExecError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("code", self.as_ref())?;
map.serialize_entry("msg", &format!("{self}"))?;
match self {
ExecError::GetCredentials(src) => map.serialize_entry("source", &src)?,
_ => serialize_upstream_err(self, &mut map)?,
}
map.end()
}
}
impl Serialize for LaunchTerminalError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("code", self.as_ref())?;
map.serialize_entry("msg", &format!("{self}"))?;
match self {
LaunchTerminalError::Exec(src) => map.serialize_entry("source", &src)?,
_ => serialize_upstream_err(self, &mut map)?,
}
map.end()
}
}

View File

@ -6,6 +6,7 @@ use crate::credentials::{Session,BaseCredentials};
use crate::errors::*;
use crate::clientinfo::Client;
use crate::state::AppState;
use crate::terminal;
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -78,3 +79,9 @@ pub async fn save_config(config: AppConfig, app_state: State<'_, AppState>) -> R
.map_err(|e| format!("Error saving config: {e}"))?;
Ok(())
}
#[tauri::command]
pub async fn launch_terminal(base: bool) -> Result<(), LaunchTerminalError> {
terminal::launch(base).await
}

View File

@ -7,4 +7,5 @@ mod clientinfo;
mod ipc;
mod state;
mod server;
mod terminal;
mod tray;

View File

@ -29,6 +29,7 @@ pub struct AppState {
pub session: RwLock<Session>,
pub request_count: RwLock<u64>,
pub open_requests: RwLock<HashMap<u64, Sender<ipc::Approval>>>,
pub pending_terminal_request: RwLock<bool>,
pub bans: RwLock<std::collections::HashSet<Option<Client>>>,
server: RwLock<Server>,
pool: sqlx::SqlitePool,
@ -41,6 +42,7 @@ impl AppState {
session: RwLock::new(session),
request_count: RwLock::new(0),
open_requests: RwLock::new(HashMap::new()),
pending_terminal_request: RwLock::new(false),
bans: RwLock::new(HashSet::new()),
server: RwLock::new(server),
pool,
@ -59,15 +61,23 @@ impl AppState {
pub async fn update_config(&self, new_config: AppConfig) -> Result<(), SetupError> {
let mut live_config = self.config.write().await;
// update autostart if necessary
if new_config.start_on_login != live_config.start_on_login {
config::set_auto_launch(new_config.start_on_login)?;
}
// rebind socket if necessary
if new_config.listen_addr != live_config.listen_addr
|| new_config.listen_port != live_config.listen_port
{
let mut sv = self.server.write().await;
sv.rebind(new_config.listen_addr, new_config.listen_port).await?;
}
// re-register hotkeys if necessary
if new_config.hotkeys.show_window != live_config.hotkeys.show_window
|| new_config.hotkeys.launch_terminal != live_config.hotkeys.launch_terminal
{
config::register_hotkeys(&new_config.hotkeys)?;
}
new_config.save(&self.pool).await?;
*live_config = new_config;
@ -141,22 +151,21 @@ impl AppState {
Ok(())
}
pub async fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
pub async fn is_unlocked(&self) -> bool {
let session = self.session.read().await;
match *session {
Session::Unlocked{ref base, ..} => Ok(serde_json::to_string(base).unwrap()),
Session::Locked(_) => Err(GetCredentialsError::Locked),
Session::Empty => Err(GetCredentialsError::Empty),
}
matches!(*session, Session::Unlocked{..})
}
pub async fn serialize_base_creds(&self) -> Result<String, GetCredentialsError> {
let app_session = self.session.read().await;
let (base, _session) = app_session.try_get()?;
Ok(serde_json::to_string(base).unwrap())
}
pub async fn serialize_session_creds(&self) -> Result<String, GetCredentialsError> {
let session = self.session.read().await;
match *session {
Session::Unlocked{ref session, ..} => Ok(serde_json::to_string(session).unwrap()),
Session::Locked(_) => Err(GetCredentialsError::Locked),
Session::Empty => Err(GetCredentialsError::Empty),
}
let app_session = self.session.read().await;
let (_bsae, session) = app_session.try_get()?;
Ok(serde_json::to_string(session).unwrap())
}
async fn new_session(&self, base: BaseCredentials) -> Result<(), GetSessionError> {
@ -165,4 +174,21 @@ impl AppState {
*app_session = Session::Unlocked {base, session};
Ok(())
}
pub async fn register_terminal_request(&self) -> Result<(), ()> {
let mut req = self.pending_terminal_request.write().await;
if *req {
// if a request is already pending, we can't register a new one
Err(())
}
else {
*req = true;
Ok(())
}
}
pub async fn unregister_terminal_request(&self) {
let mut req = self.pending_terminal_request.write().await;
*req = false;
}
}

82
src-tauri/src/terminal.rs Normal file
View File

@ -0,0 +1,82 @@
use std::process::Command;
use tauri::Manager;
use crate::app::APP;
use crate::errors::*;
use crate::state::AppState;
pub async fn launch(use_base: bool) -> Result<(), LaunchTerminalError> {
let app = APP.get().unwrap();
let state = app.state::<AppState>();
// register_terminal_request() returns Err if there is another request pending
if state.register_terminal_request().await.is_err() {
return Ok(());
}
let mut cmd = {
let config = state.config.read().await;
let mut cmd = Command::new(&config.terminal.exec);
cmd.args(&config.terminal.args);
cmd
};
// if session is unlocked or empty, wait for credentials from frontend
if !state.is_unlocked().await {
app.emit_all("launch-terminal-request", ())?;
let window = app.get_window("main")
.ok_or(LaunchTerminalError::NoMainWindow)?;
if !window.is_visible()? {
window.unminimize()?;
window.show()?;
}
window.set_focus()?;
let (tx, rx) = tokio::sync::oneshot::channel();
app.once_global("credentials-event", move |e| {
let success = match e.payload() {
Some("\"unlocked\"") | Some("\"entered\"") => true,
_ => false,
};
let _ = tx.send(success);
});
if !rx.await.unwrap_or(false) {
state.unregister_terminal_request().await;
return Ok(()); // request was canceled by user
}
}
// more lock-management
{
let app_session = state.session.read().await;
// session should really be unlocked at this point, but if the frontend misbehaves
// (i.e. lies about unlocking) we could end up here with a locked session
// this will result in an error popup to the user (see main hotkey handler)
let (base_creds, session_creds) = app_session.try_get()?;
if use_base {
cmd.env("AWS_ACCESS_KEY_ID", &base_creds.access_key_id);
cmd.env("AWS_SECRET_ACCESS_KEY", &base_creds.secret_access_key);
}
else {
cmd.env("AWS_ACCESS_KEY_ID", &session_creds.access_key_id);
cmd.env("AWS_SECRET_ACCESS_KEY", &session_creds.secret_access_key);
cmd.env("AWS_SESSION_TOKEN", &session_creds.token);
}
}
let res = match cmd.spawn() {
Ok(_) => Ok(()),
Err(e) if std::io::ErrorKind::NotFound == e.kind() => {
Err(ExecError::NotFound(cmd.get_program().to_owned()))
},
Err(e) => Err(ExecError::ExecutionFailed(e)),
};
state.unregister_terminal_request().await;
res?; // ? auto-conversion is more liberal than .into()
Ok(())
}

View File

@ -8,11 +8,12 @@
},
"package": {
"productName": "creddy",
"version": "0.2.2"
"version": "0.3.0"
},
"tauri": {
"allowlist": {
"os": {"all": true}
"os": {"all": true},
"dialog": {"open": true}
},
"bundle": {
"active": true,

View File

@ -16,6 +16,20 @@ listen('credentials-request', (tauriEvent) => {
$appState.pendingRequests.put(tauriEvent.payload);
});
listen('launch-terminal-request', async (tauriEvent) => {
if ($appState.currentRequest === null) {
let status = await invoke('get_session_status');
if (status === 'locked') {
navigate('Unlock');
}
else if (status === 'empty') {
navigate('EnterCredentials');
}
// else, session is unlocked, so do nothing
// (although we shouldn't even get the event in that case)
}
})
acceptRequest();
</script>

View File

@ -9,6 +9,10 @@ export default function() {
resolvers: [],
size() {
return this.items.length;
},
put(item) {
this.items.push(item);
let resolver = this.resolvers.shift();

13
src/ui/KeyCombo.svelte Normal file
View File

@ -0,0 +1,13 @@
<script>
export let keys;
</script>
<div class="flex gap-x-[0.2em] items-center">
{#each keys as key, i}
{#if i > 0}
<span class="mt-[-0.1em]">+</span>
{/if}
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">{key}</kbd>
{/each}
</div>

View File

@ -1,113 +1,42 @@
<script>
export let color = 'base-content';
export let thickness = '2px';
export let thickness = 8;
let classes = '';
export { classes as class };
const colorVars = {
'primary': 'p',
'primary-focus': 'pf',
'primary-content': 'pc',
'secondary': 's',
'secondary-focus': 'sf',
'secondary-content': 'sc',
'accent': 'a',
'accent-focus': 'af',
'accent-content': 'ac',
'neutral': 'n',
'neutral-focus': 'nf',
'neutral-content': 'nc',
'base-100': 'b1',
'base-200': 'b2',
'base-300': 'b3',
'base-content': 'bc',
'info': 'in',
'info-content': 'inc',
'success': 'su',
'success-content': 'suc',
'warning': 'wa',
'warning-content': 'wac',
'error': 'er',
'error-content': 'erc',
}
let arcStyle = `border-width: ${thickness};`;
arcStyle += `border-color: hsl(var(--${colorVars[color]})) transparent transparent transparent;`;
const radius = (100 - thickness) / 2;
// the px are fake, but we need them to satisfy css calc()
const circumference = `${2 * Math.PI * radius}px`;
</script>
<style>
#spinner {
position: relative;
animation: spin;
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
<svg
style:--circumference={circumference}
class={classes}
viewBox="0 0 100 100"
stroke="currentColor"
>
<circle cx="50" cy="50" r={radius} stroke-width={thickness} />
</svg>
<style>
circle {
fill: transparent;
stroke-dasharray: var(--circumference);
transform: rotate(-90deg);
transform-origin: center;
animation: chase 3s infinite,
spin 1.5s linear infinite;
}
@keyframes chase {
0% { stroke-dashoffset: calc(-1 * var(--circumference)); }
50% { stroke-dashoffset: calc(-2 * var(--circumference)); }
100% { stroke-dashoffset: calc(-3 * var(--circumference)); }
}
@keyframes spin {
50% { transform: rotate(225deg); }
100% { transform: rotate(360deg); }
50% { transform: rotate(135deg); }
100% { transform: rotate(270deg); }
}
.arc {
position: absolute;
top: 0;
left: 0;
border-radius: 9999px;
}
.arc-top {
transform: rotate(-45deg);
}
.arc-right {
animation: spin-right;
animation-duration: 3s;
animation-iteration-count: infinite;
}
.arc-bottom {
animation: spin-bottom;
animation-duration: 3s;
animation-iteration-count: infinite;
}
.arc-left {
animation: spin-left;
animation-duration: 3s;
animation-iteration-count: infinite;
}
@keyframes spin-top {
0% { transform: rotate(-45deg); }
50% { transform: rotate(315deg); }
100% { transform: rotate(-45deg); }
}
@keyframes spin-right {
0% { transform: rotate(45deg); }
50% { transform: rotate(315deg); }
100% { transform: rotate(405deg); }
}
@keyframes spin-bottom {
0% { transform: rotate(135deg); }
50% { transform: rotate(315deg); }
100% { transform: rotate(495deg); }
}
@keyframes spin-left {
0% { transform: rotate(225deg); }
50% { transform: rotate(315deg); }
100% { transform: rotate(585deg); }
}
</style>
<div id="spinner" class="w-6 h-6 {classes}">
<div class="arc arc-top w-full h-full" style={arcStyle}></div>
<div class="arc arc-right w-full h-full" style={arcStyle}></div>
<div class="arc arc-bottom w-full h-full" style={arcStyle}></div>
<div class="arc arc-left w-full h-full" style={arcStyle}></div>
</div>
</style>

View File

@ -0,0 +1,27 @@
<script>
import { createEventDispatcher } from 'svelte';
import { open } from '@tauri-apps/api/dialog';
import Setting from './Setting.svelte';
export let title;
export let value;
const dispatch = createEventDispatcher();
</script>
<Setting {title}>
<div slot="input">
<input
type="text"
class="input input-sm input-bordered grow text-right"
bind:value
on:change={() => dispatch('update', {value})}
>
<button
class="btn btn-sm btn-primary"
on:click={async () => value = await open()}
>Browse</button>
</div>
<slot name="description" slot="description"></slot>
</Setting>

View File

@ -0,0 +1,61 @@
<script>
import { createEventDispatcher } from 'svelte';
import KeyCombo from '../KeyCombo.svelte';
export let description;
export let value;
const id = Math.random().toString().slice(2);
const dispatch = createEventDispatcher();
let listening = false;
function listen() {
// don't re-listen if we already are
if (listening) return;
listening = true;
window.addEventListener('keyup', setKeybind, {once: true});
// setTimeout avoids reacting to the click event that we are currently processing
setTimeout(() => window.addEventListener('click', cancel, {once: true}), 0);
}
function setKeybind(event) {
console.log(event);
let keys = [];
if (event.ctrlKey) keys.push('ctrl');
if (event.altKey) keys.push('alt');
if (event.metaKey) keys.push('meta');
if (event.shiftKey) keys.push('shift');
keys.push(event.key);
value.keys = keys.join('+');
dispatch('update', {value});
listening = false;
window.removeEventListener('click', cancel, {once: true});
event.preventDefault();
event.stopPropagation();
}
function cancel() {
listening = false;
window.removeEventListener('keyup', setKeybind, {once: true});
}
</script>
<input
{id}
type="checkbox"
class="checkbox checkbox-primary"
bind:checked={value.enabled}
on:change={() => dispatch('update', {value})}
>
<label for={id} class="cursor-pointer ml-4 text-lg">{description}</label>
<button class="h-12 p-2 rounded border border-neutral cursor-pointer text-center" on:click={listen}>
{#if listening}
Click to cancel
{:else}
<KeyCombo keys={value.keys.split('+')} />
{/if}
</button>

View File

@ -5,6 +5,7 @@
export let title;
export let value;
export let unit = '';
export let min = null;
export let max = null;

View File

@ -6,14 +6,17 @@
</script>
<div class="divider"></div>
<div class="flex justify-between">
<h3 class="text-lg font-bold">{title}</h3>
<slot name="input"></slot>
</div>
<div>
<div class="flex flex-wrap justify-between gap-y-4">
<h3 class="text-lg font-bold shrink-0">{title}</h3>
{#if $$slots.input}
<slot name="input"></slot>
{/if}
</div>
{#if $$slots.description}
<p class="mt-3">
<slot name="description"></slot>
</p>
{/if}
{#if $$slots.description}
<p class="mt-3">
<slot name="description"></slot>
</p>
{/if}
</div>

View File

@ -0,0 +1,14 @@
<script>
export let name;
</script>
<div>
<div class="divider mt-0 mb-8">
<h2 class="text-xl font-bold">{name}</h2>
</div>
<div class="space-y-12">
<slot></slot>
</div>
</div>

View File

@ -0,0 +1,22 @@
<script>
import { createEventDispatcher } from 'svelte';
import Setting from './Setting.svelte';
export let title;
export let value;
const dispatch = createEventDispatcher();
</script>
<Setting {title}>
<div slot="input">
<input
type="text"
class="input input-sm input-bordered grow text-right"
bind:value
on:change={() => dispatch('update', {value})}
>
</div>
<slot name="description" slot="description"></slot>
</Setting>

View File

@ -1,3 +1,5 @@
export { default as Setting } from './Setting.svelte';
export { default as ToggleSetting } from './ToggleSetting.svelte';
export { default as NumericSetting } from './NumericSetting.svelte';
export { default as FileSetting } from './FileSetting.svelte';
export { default as TextSetting } from './TextSetting.svelte';

View File

@ -6,6 +6,7 @@
import { appState, completeRequest } from '../lib/state.js';
import ErrorAlert from '../ui/ErrorAlert.svelte';
import Link from '../ui/Link.svelte';
import KeyCombo from '../ui/KeyCombo.svelte';
// Send response to backend, display error if applicable
@ -108,17 +109,15 @@
<div class="w-full flex justify-between">
<Link target={deny} hotkey="Escape">
<button class="btn btn-error justify-self-start">
Deny
<kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Esc</kbd>
<span class="mr-2">Deny</span>
<KeyCombo keys={['Esc']} />
</button>
</Link>
<Link target={approve} hotkey="Enter" shift="{true}">
<button class="btn btn-success justify-self-end">
Approve
<kbd class="ml-2 normal-case px-1 py-0.5 rounded border border-neutral">Shift</kbd>
<span class="mx-0.5">+</span>
<kbd class="normal-case px-1 py-0.5 rounded border border-neutral">Enter</kbd>
<span class="mr-2">Approve</span>
<KeyCombo keys={['Shift', 'Enter']} />
</button>
</Link>
</div>

View File

@ -31,6 +31,7 @@
try {
saving = true;
await invoke('save_credentials', {credentials, passphrase});
emit('credentials-event', 'entered');
if ($appState.currentRequest) {
navigate('Approve');
}
@ -39,14 +40,16 @@
}
}
catch (e) {
if (e.code === "GetSession") {
let root = getRootCause(e);
window.error = e;
const root = getRootCause(e);
if (e.code === 'GetSession' && root.code) {
errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
}
else {
errorMsg = e.msg;
}
// if the alert already existed, shake it
if (alert) {
alert.shake();
}
@ -54,6 +57,11 @@
saving = false;
}
}
function cancel() {
emit('credentials-event', 'enter-canceled');
navigate('Home');
}
</script>
@ -71,13 +79,13 @@
<input type="password" placeholder="Re-enter passphrase" bind:value={confirmPassphrase} class="input input-bordered" on:change={confirm} />
<button type="submit" class="btn btn-primary">
{#if saving}
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
{#if saving }
<Spinner class="w-5 h-5" thickness="12"/>
{:else}
Submit
{/if}
</button>
<Link target="Home" hotkey="Escape">
<Link target={cancel} hotkey="Escape">
<button class="btn btn-sm btn-outline w-full">Cancel</button>
</Link>
</form>

View File

@ -10,13 +10,11 @@
import vaultDoorSvg from '../assets/vault_door.svg?raw';
// onMount(async () => {
// // will block until a request comes in
// let req = await $appState.pendingRequests.get();
// $appState.currentRequest = req;
// navigate('Approve');
// });
let launchBase = false;
function launchTerminal() {
invoke('launch_terminal', {base: launchBase});
launchBase = false;
}
</script>
@ -25,25 +23,32 @@
</Nav>
<div class="flex flex-col h-screen items-center justify-center p-4 space-y-4">
{#await invoke('get_session_status') then status}
{#if status === 'locked'}
<div class="flex flex-col items-center space-y-4">
{@html vaultDoorSvg}
{#await invoke('get_session_status') then status}
{#if status === 'locked'}
{@html vaultDoorSvg}
<h2 class="text-2xl font-bold">Creddy is locked</h2>
<Link target="Unlock" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Unlock</button>
</Link>
<h2 class="text-2xl font-bold">Creddy is locked</h2>
<Link target="Unlock" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Unlock</button>
</Link>
{:else if status === 'unlocked'}
{@html vaultDoorSvg}
<h2 class="text-2xl font-bold">Waiting for requests</h2>
{:else if status === 'unlocked'}
<h2 class="text-2xl font-bold">Waiting for requests</h2>
<button class="btn btn-primary w-full" on:click={launchTerminal}>
Launch Terminal
</button>
<label class="label cursor-pointer flex items-center space-x-2">
<input type="checkbox" class="checkbox checkbox-sm" bind:checked={launchBase}>
<span class="label-text">Launch with base credentials</span>
</label>
{:else if status === 'empty'}
{@html vaultDoorSvg}
<h2 class="text-2xl font-bold">No credentials found</h2>
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Enter Credentials</button>
</Link>
{/if}
{/await}
{:else if status === 'empty'}
<h2 class="text-2xl font-bold">No credentials found</h2>
<Link target="EnterCredentials" hotkey="Enter" class="w-64">
<button class="btn btn-primary w-full">Enter Credentials</button>
</Link>
{/if}
{/await}
</div>
</div>

View File

@ -6,7 +6,9 @@
import Nav from '../ui/Nav.svelte';
import Link from '../ui/Link.svelte';
import ErrorAlert from '../ui/ErrorAlert.svelte';
import { Setting, ToggleSetting, NumericSetting } from '../ui/settings';
import SettingsGroup from '../ui/settings/SettingsGroup.svelte';
import Keybind from '../ui/settings/Keybind.svelte';
import { Setting, ToggleSetting, NumericSetting, FileSetting, TextSetting } from '../ui/settings';
import { fly } from 'svelte/transition';
import { backInOut } from 'svelte/easing';
@ -14,6 +16,7 @@
let error = null;
async function save() {
console.log('updating config');
try {
await invoke('save_config', {config: $appState.config});
}
@ -23,59 +26,81 @@
}
}
let osType = '';
let osType = null;
type().then(t => osType = t);
</script>
<Nav>
<h2 slot="title" class="text-2xl font-bold">Settings</h2>
<h1 slot="title" class="text-2xl font-bold">Settings</h1>
</Nav>
{#await invoke('get_config') then config}
<div class="max-w-md mx-auto mt-1.5 p-4">
<!-- <h2 class="text-2xl font-bold text-center">Settings</h2> -->
<div class="max-w-lg mx-auto mt-1.5 p-4 space-y-16">
<SettingsGroup name="General">
<ToggleSetting title="Start on login" bind:value={$appState.config.start_on_login} on:update={save}>
<svelte:fragment slot="description">
Start Creddy when you log in to your computer.
</svelte:fragment>
</ToggleSetting>
<ToggleSetting title="Start on login" bind:value={$appState.config.start_on_login} on:update={save}>
<svelte:fragment slot="description">
Start Creddy when you log in to your computer.
</svelte:fragment>
</ToggleSetting>
<ToggleSetting title="Start minimized" bind:value={$appState.config.start_minimized} on:update={save}>
<svelte:fragment slot="description">
Minimize to the system tray at startup.
</svelte:fragment>
</ToggleSetting>
<ToggleSetting title="Start minimized" bind:value={$appState.config.start_minimized} on:update={save}>
<svelte:fragment slot="description">
Minimize to the system tray at startup.
</svelte:fragment>
</ToggleSetting>
<NumericSetting title="Re-hide delay" bind:value={$appState.config.rehide_ms} min={0} unit="Milliseconds" on:update={save}>
<svelte:fragment slot="description">
How long to wait after a request is approved/denied before minimizing
the window to tray. Only applicable if the window was minimized
to tray before the request was received.
</svelte:fragment>
</NumericSetting>
<NumericSetting title="Re-hide delay" bind:value={$appState.config.rehide_ms} min={0} unit="Milliseconds" on:update={save}>
<svelte:fragment slot="description">
How long to wait after a request is approved/denied before minimizing
the window to tray. Only applicable if the window was minimized
to tray before the request was received.
</svelte:fragment>
</NumericSetting>
<NumericSetting
title="Listen port"
bind:value={$appState.config.listen_port}
min={osType === 'Windows_NT' ? 1 : 0}
on:update={save}
>
<svelte:fragment slot="description">
Listen for credentials requests on this port.
(Should be used with <code>$AWS_CONTAINER_CREDENTIALS_FULL_URI</code>)
</svelte:fragment>
</NumericSetting>
<NumericSetting
title="Listen port"
bind:value={$appState.config.listen_port}
min={osType === 'Windows_NT' ? 1 : 0}
on:update={save}
>
<svelte:fragment slot="description">
Listen for credentials requests on this port.
(Should be used with <code>$AWS_CONTAINER_CREDENTIALS_FULL_URI</code>)
</svelte:fragment>
</NumericSetting>
<Setting title="Update credentials">
<Link slot="input" target="EnterCredentials">
<button class="btn btn-sm btn-primary">Update</button>
</Link>
<svelte:fragment slot="description">
Update or re-enter your encrypted credentials.
</svelte:fragment>
</Setting>
<FileSetting
title="Terminal emulator"
bind:value={$appState.config.terminal.exec}
on:update={save}
>
<svelte:fragment slot="description">
Choose your preferred terminal emulator (e.g. <code>gnome-terminal</code> or <code>wt.exe</code>.) May be an absolute path or an executable discoverable on <code>$PATH</code>.
</svelte:fragment>
</FileSetting>
</SettingsGroup>
<SettingsGroup name="Hotkeys">
<div class="space-y-4">
<p>Click on a keybinding to modify it. Use the checkbox to enable or disable a keybinding entirely.</p>
<div class="grid grid-cols-[auto_1fr_auto] gap-y-3 items-center">
<Keybind description="Show Creddy" value={$appState.config.hotkeys.show_window} on:update={save} />
<Keybind description="Launch terminal" value={$appState.config.hotkeys.launch_terminal} on:update={save} />
</div>
</div>
</SettingsGroup>
<Setting title="Update credentials">
<Link slot="input" target="EnterCredentials">
<button class="btn btn-sm btn-primary">Update</button>
</Link>
<svelte:fragment slot="description">
Update or re-enter your encrypted credentials.
</svelte:fragment>
</Setting>
</div>
{/await}

View File

@ -1,5 +1,6 @@
<script>
import { invoke } from '@tauri-apps/api/tauri';
import { emit } from '@tauri-apps/api/event';
import { onMount } from 'svelte';
import { appState } from '../lib/state.js';
@ -26,6 +27,7 @@
saving = true;
let r = await invoke('unlock', {passphrase});
$appState.credentialStatus = 'unlocked';
emit('credentials-event', 'unlocked');
if ($appState.currentRequest) {
navigate('Approve');
}
@ -34,23 +36,28 @@
}
}
catch (e) {
window.error = e;
if (e.code === 'GetSession') {
let root = getRootCause(e);
const root = getRootCause(e);
if (e.code === 'GetSession' && root.code) {
errorMsg = `Error response from AWS (${root.code}): ${root.msg}`;
}
else {
errorMsg = e.msg;
}
// if the alert already existed, shake it
if (alert) {
alert.shake();
}
saving = true;
saving = false;
}
}
function cancel() {
emit('credentials-event', 'unlock-canceled');
navigate('Home');
}
onMount(() => {
loadTime = Date.now();
})
@ -69,13 +76,13 @@
<button type="submit" class="btn btn-primary">
{#if saving}
<Spinner class="w-5 h-5" color="primary-content" thickness="2px"/>
<Spinner class="w-5 h-5" thickness="12"/>
{:else}
Submit
{/if}
</button>
<Link target="Home" hotkey="Escape">
<button class="btn btn-outline btn-sm w-full">Cancel</button>
<Link target={cancel} hotkey="Escape">
<button class="btn btn-sm btn-outline w-full">Cancel</button>
</Link>
</form>

16
todo.md Normal file
View File

@ -0,0 +1,16 @@
## Definitely
* Switch to "process" provider for AWS credentials (much less hacky)
* Session timeout (plain duration, or activity-based?)
* Fix rehide behavior when new request comes in while old one is still being resolved
* Additional hotkey configuration (approve/deny at the very least)
* Logging
* Icon
* SSH key handling
## Maybe
* Flatten error type hierarchy
* Rehide after terminal launch from locked
* Generalize Request across both credentials and terminal launch?
* Make hotkey configuration a little more tolerant of slight mistiming