mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-29 13:44:47 +00:00
Compare commits
93 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4106d97f6b | ||
![]() |
98103fd139 | ||
![]() |
9453e8ba7b | ||
![]() |
2f78575cd7 | ||
![]() |
500acee064 | ||
![]() |
42eb5f6b78 | ||
![]() |
ef19b92e85 | ||
![]() |
f263f954d4 | ||
![]() |
2ce0f03282 | ||
![]() |
150999d3a3 | ||
![]() |
8cc76555d2 | ||
![]() |
f0f8f06890 | ||
![]() |
176a55c91d | ||
![]() |
fc6dfc50dd | ||
![]() |
34d020f66a | ||
![]() |
fa0ef69c46 | ||
![]() |
e5c1e421f7 | ||
![]() |
f3994f1bd9 | ||
![]() |
6956ef9e0f | ||
![]() |
a080129882 | ||
![]() |
ef9bfe6120 | ||
![]() |
37d69e858f | ||
![]() |
ee594f5bcd | ||
![]() |
adf022de2c | ||
![]() |
c5a9b890c4 | ||
![]() |
2d1a96a12b | ||
![]() |
5289981485 | ||
![]() |
a89047b205 | ||
![]() |
a7b4496d22 | ||
![]() |
09cd9d0e18 | ||
![]() |
fb2a4d268d | ||
![]() |
9b61615701 | ||
![]() |
077d2421e1 | ||
![]() |
202ba18a8c | ||
![]() |
63f33f8f4b | ||
![]() |
3bd89a0194 | ||
![]() |
604bc28c9a | ||
![]() |
f81f5d122a | ||
![]() |
3633be750e | ||
![]() |
404fd72ea9 | ||
![]() |
402b76bcc9 | ||
![]() |
b6c97ffa49 | ||
![]() |
20aa1d814f | ||
![]() |
786daaac32 | ||
![]() |
0360ad2dd0 | ||
![]() |
0a451c5876 | ||
![]() |
5a9625424c | ||
![]() |
62c1f6463b | ||
![]() |
9fe82f2c0a | ||
![]() |
09838197a2 | ||
![]() |
27114797a2 | ||
![]() |
4dc77d11cf | ||
![]() |
245698b67d | ||
![]() |
017fabaf6f | ||
![]() |
6c11189b3e | ||
![]() |
dd70f5f5d8 | ||
![]() |
47277ac5aa | ||
![]() |
b69cbbcdd1 | ||
![]() |
efba980a1d | ||
![]() |
f31da67508 | ||
![]() |
2ba76cc0b9 | ||
![]() |
62d14ac0cb | ||
![]() |
c1b4ffd248 | ||
![]() |
87cacdb568 | ||
![]() |
2a11bc4fcc | ||
![]() |
f716baa7d4 | ||
![]() |
5b60daf366 | ||
![]() |
11f9f4e824 | ||
![]() |
0daf48f699 | ||
![]() |
8fb0ea4d75 | ||
![]() |
d9948cf6e2 | ||
![]() |
54b618cffc | ||
![]() |
690dde628e | ||
![]() |
3dfbcf9d41 | ||
![]() |
d91ba71ec0 | ||
![]() |
99698913a8 | ||
![]() |
0dbb16d859 | ||
![]() |
0f8cff2d5b | ||
![]() |
471f9effcf | ||
![]() |
04faf1a04a | ||
![]() |
656f5c2561 | ||
![]() |
99bc2c1c65 | ||
![]() |
c8735243f3 | ||
![]() |
b197a16e5c | ||
![]() |
1a361e67b3 | ||
![]() |
fc471b2c16 | ||
![]() |
ae17faa7e5 | ||
![]() |
5fb70f1812 | ||
![]() |
03fc68bb6d | ||
![]() |
bb9c80623d | ||
![]() |
4dd0a5951f | ||
![]() |
b010791767 | ||
![]() |
ef61a141a6 |
@@ -225,6 +225,15 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LeSeulArtichaut",
|
||||
"name": "LeSeulArtichaut",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/38361244?v=4",
|
||||
"profile": "https://github.com/LeSeulArtichaut",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
@@ -232,5 +241,6 @@
|
||||
"projectOwner": "Eugeny",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"commitConvention": "none"
|
||||
"commitConvention": "none",
|
||||
"skipCi": true
|
||||
}
|
||||
|
@@ -97,3 +97,4 @@ rules:
|
||||
'@typescript-eslint/no-untyped-public-signature': off # bugs out on constructors
|
||||
'@typescript-eslint/restrict-template-expressions': off
|
||||
'@typescript-eslint/no-dynamic-delete': off
|
||||
'@typescript-eslint/prefer-nullish-coalescing': off
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,3 +24,7 @@ yarn-error.log
|
||||
docs/api
|
||||
.travis.ssh.key
|
||||
*.code-workspace
|
||||
|
||||
.electron-symbols
|
||||
sentry.properties
|
||||
sentry-symbols.js
|
||||
|
54
README.md
54
README.md
@@ -66,42 +66,46 @@ See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) and
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt="Russell Myers"/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt="Austin Warren"/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt="Felicia Hummel"/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt="Mike MacCana"/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt="Yacine Kanzari"/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt="BBJip"/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt="Futagirl"/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt="Levin Rickert"/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt="OJ Kwon"/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt="domain"/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt="James Brumond"/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt="Daniel Imms"/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt="Florian Bachmann"/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt="Michael Kühnel"/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt="Tilmann Meyer"/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt="PM Extra"/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt="Jonathan"/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt="Hans Koch"/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt="Dak Smyth"/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt="Wang Zhi"/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt="jack1142"/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt="Howie Douglas"/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt="Chris Kaczor"/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt="Johannes Kadak"/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
@@ -13,16 +13,18 @@ export class Application {
|
||||
})
|
||||
|
||||
const configData = loadConfig()
|
||||
if (process.platform === 'linux' && ((configData.appearance || {}).opacity || 1) !== 1) {
|
||||
app.commandLine.appendSwitch('enable-transparent-visuals')
|
||||
app.disableHardwareAcceleration()
|
||||
if (process.platform === 'linux') {
|
||||
app.commandLine.appendSwitch('no-sandbox')
|
||||
if (((configData.appearance || {}).opacity || 1) !== 1) {
|
||||
app.commandLine.appendSwitch('enable-transparent-visuals')
|
||||
app.disableHardwareAcceleration()
|
||||
}
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('disable-http-cache')
|
||||
app.commandLine.appendSwitch('lang', 'EN')
|
||||
|
||||
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
|
||||
console.log('Setting Electron flag:', flag.join('='))
|
||||
app.commandLine.appendSwitch(flag[0], flag[1])
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import './sentry'
|
||||
import './lru'
|
||||
import { app, ipcMain, Menu } from 'electron'
|
||||
import { parseArgs } from './cli'
|
||||
@@ -46,7 +47,7 @@ if (argv.d) {
|
||||
electronDebug({
|
||||
isEnabled: true,
|
||||
showDevTools: true,
|
||||
devToolsMode: 'undocked'
|
||||
devToolsMode: 'undocked',
|
||||
})
|
||||
}
|
||||
|
||||
|
18
app/lib/sentry.ts
Normal file
18
app/lib/sentry.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
const { init } = process.type === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
||||
|
||||
|
||||
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||
let release
|
||||
try {
|
||||
release = require('electron').app.getVersion()
|
||||
} catch {
|
||||
release = require('electron').remote.app.getVersion()
|
||||
}
|
||||
|
||||
init({
|
||||
dsn: SENTRY_DSN,
|
||||
release,
|
||||
integrations (integrations) {
|
||||
return integrations.filter(integration => integration.name !== 'Breadcrumbs')
|
||||
},
|
||||
})
|
@@ -3,6 +3,7 @@ import { debounceTime } from 'rxjs/operators'
|
||||
import { BrowserWindow, app, ipcMain, Rectangle, screen } from 'electron'
|
||||
import ElectronConfig = require('electron-config')
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
|
||||
import { loadConfig } from './config'
|
||||
|
||||
@@ -46,6 +47,7 @@ export class Window {
|
||||
minHeight: 300,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: path.join(__dirname, 'sentry.js'),
|
||||
},
|
||||
frame: false,
|
||||
show: false,
|
||||
@@ -147,14 +149,14 @@ export class Window {
|
||||
this.window.webContents.send(event, ...args)
|
||||
}
|
||||
|
||||
isDestroyed() {
|
||||
isDestroyed () {
|
||||
return !this.window || this.window.isDestroyed();
|
||||
}
|
||||
|
||||
private setupWindowManagement () {
|
||||
this.window.on('show', () => {
|
||||
this.visible.next(true)
|
||||
this.window.webContents.send('host:window-shown')
|
||||
this.send('host:window-shown')
|
||||
})
|
||||
|
||||
this.window.on('hide', () => {
|
||||
@@ -164,20 +166,20 @@ export class Window {
|
||||
let moveSubscription = new Observable<void>(observer => {
|
||||
this.window.on('move', () => observer.next())
|
||||
}).pipe(debounceTime(250)).subscribe(() => {
|
||||
this.window.webContents.send('host:window-moved')
|
||||
this.send('host:window-moved')
|
||||
})
|
||||
|
||||
this.window.on('closed', () => {
|
||||
moveSubscription.unsubscribe()
|
||||
})
|
||||
|
||||
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
||||
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
||||
this.window.on('enter-full-screen', () => this.send('host:window-enter-full-screen'))
|
||||
this.window.on('leave-full-screen', () => this.send('host:window-leave-full-screen'))
|
||||
|
||||
this.window.on('close', event => {
|
||||
if (!this.closing) {
|
||||
event.preventDefault()
|
||||
this.window.webContents.send('host:window-close-request')
|
||||
this.send('host:window-close-request')
|
||||
return
|
||||
}
|
||||
this.windowConfig.set('windowBoundaries', this.windowBounds)
|
||||
|
@@ -42,13 +42,13 @@
|
||||
"optionalDependencies": {
|
||||
"macos-native-processlist": "^1.0.2",
|
||||
"windows-blurbehind": "^1.0.1",
|
||||
"windows-native-registry": "^1.0.15",
|
||||
"windows-native-registry": "^1.0.17",
|
||||
"windows-process-tree": "^0.2.4",
|
||||
"windows-swca": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mz": "0.0.32",
|
||||
"@types/node": "12.7.12",
|
||||
"node-abi": "^2.12.0"
|
||||
"node-abi": "^2.13.0"
|
||||
}
|
||||
}
|
||||
|
@@ -6,33 +6,3 @@ import '@fortawesome/fontawesome-free/css/brands.css'
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
||||
import 'ngx-toastr/toastr.css'
|
||||
import './preload.scss'
|
||||
|
||||
import * as Raven from 'raven-js'
|
||||
|
||||
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||
|
||||
Raven.config(
|
||||
SENTRY_DSN,
|
||||
{
|
||||
release: require('electron').remote.app.getVersion(),
|
||||
dataCallback: (data: any) => {
|
||||
const normalize = (filename: string) => {
|
||||
const splitArray = filename.split('/')
|
||||
return splitArray[splitArray.length - 1]
|
||||
}
|
||||
|
||||
data.exception.values[0].stacktrace.frames.forEach((frame: any) => {
|
||||
frame.filename = normalize(frame.filename)
|
||||
})
|
||||
|
||||
data.culprit = data.exception.values[0].stacktrace.frames[0].filename
|
||||
|
||||
return data
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
process.on('uncaughtException' as any, (err) => {
|
||||
Raven.captureException(err as any)
|
||||
console.error(err)
|
||||
})
|
||||
|
@@ -10,6 +10,10 @@
|
||||
background-image: none;
|
||||
width: auto;
|
||||
|
||||
&.toast-error {
|
||||
background-color: #BD362F;
|
||||
}
|
||||
|
||||
&.toast-info {
|
||||
background-color: #555;
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ module.exports = {
|
||||
target: 'node',
|
||||
entry: {
|
||||
'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
|
||||
sentry: path.resolve(__dirname, 'lib/sentry.ts'),
|
||||
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
|
||||
bundle: path.resolve(__dirname, 'src/entry.ts'),
|
||||
},
|
||||
@@ -78,5 +79,8 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.type': '"renderer"'
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
@@ -45,5 +45,8 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.type': '"main"',
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
@@ -1925,10 +1925,10 @@ ngx-toastr@^10.2.0:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
node-abi@^2.12.0, node-abi@^2.7.0:
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.12.0.tgz#40e9cfabdda1837863fa825e7dfa0b15686adf6f"
|
||||
integrity sha512-VhPBXCIcvmo/5K8HPmnWJyyhvgKxnHTUMXR/XwGHV68+wrgkzST4UmQrY/XszSWA5dtnXpNp528zkcyJ/pzVcw==
|
||||
node-abi@^2.13.0, node-abi@^2.7.0:
|
||||
version "2.13.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.13.0.tgz#e2f2ec444d0aca3ea1b3874b6de41d1665828f63"
|
||||
integrity sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA==
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
@@ -3397,10 +3397,10 @@ windows-blurbehind@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.1.tgz#ff098713873304e38330b2c54cc41bb369b587b9"
|
||||
integrity sha512-1HzHfCiM1ayrbACJu5qE9zELV24uX/tINT6kxaZwLY3rtQAoeav6x9z7LFHWoLaGDN/sYbnK+9Vk0cz7fsk5HQ==
|
||||
|
||||
windows-native-registry@^1.0.15:
|
||||
version "1.0.15"
|
||||
resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-1.0.15.tgz#02593331fb7dcab99ef3ac9dd71f2c71ae57b189"
|
||||
integrity sha512-uIsz1y3LrKPkphcZsezThz07FW7Vm00Zfa6ZU88rIo2zilOLE6Ui75jh6UkBAMso8xJeyvYbbcxF9kr4Zt8Iuw==
|
||||
windows-native-registry@^1.0.17:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/windows-native-registry/-/windows-native-registry-1.0.17.tgz#d8cce48b364703a55c226690431b325114405022"
|
||||
integrity sha512-u9Fp9TyDo5dvhlW6hYBOdHPETtAahXKxo3jeW5EXwNK7qa+nSNopQycN1drtBVWe3jpJXvyKpt9zrjiDd+u4JQ==
|
||||
dependencies:
|
||||
nan "^2.14.0"
|
||||
|
||||
|
38
package.json
38
package.json
@@ -1,25 +1,29 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"@sentry/cli": "^1.49.0",
|
||||
"@sentry/electron": "^1.0.0",
|
||||
"@types/electron-config": "^3.2.2",
|
||||
"@types/electron-debug": "^2.1.0",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/webpack-env": "1.14.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.8.0",
|
||||
"@typescript-eslint/parser": "^2.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.13.0",
|
||||
"@typescript-eslint/parser": "^2.13.0",
|
||||
"apply-loader": "2.0.0",
|
||||
"awesome-typescript-loader": "^5.0.0",
|
||||
"core-js": "^3.4.2",
|
||||
"core-js": "^3.6.1",
|
||||
"cross-env": "6.0.3",
|
||||
"css-loader": "3.2.0",
|
||||
"electron": "^7.1.2",
|
||||
"css-loader": "3.4.0",
|
||||
"electron": "^7.1.7",
|
||||
"electron-builder": "22.1.0",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^4.1.0",
|
||||
"electron-notarize": "^0.1.1",
|
||||
"electron-rebuild": "^1.8.5",
|
||||
"eslint": "^6.7.1",
|
||||
"file-loader": "^4.3.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-import": "^2.19.1",
|
||||
"file-loader": "^5.0.2",
|
||||
"graceful-fs": "^4.2.2",
|
||||
"html-loader": "0.5.5",
|
||||
"json-loader": "0.5.7",
|
||||
@@ -33,21 +37,20 @@
|
||||
"pug-lint": "^2.6.0",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-static-loader": "2.0.0",
|
||||
"raven-js": "3.27.2",
|
||||
"raw-loader": "3.1.0",
|
||||
"raw-loader": "4.0.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"shelljs": "0.8.3",
|
||||
"source-code-pro": "^2.30.2",
|
||||
"source-sans-pro": "3.6.0",
|
||||
"style-loader": "^1.0.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"to-string-loader": "1.1.6",
|
||||
"tslib": "^1.10.0",
|
||||
"typedoc": "^0.15.3",
|
||||
"typescript": "^3.6.4",
|
||||
"url-loader": "^2.3.0",
|
||||
"val-loader": "2.0.1",
|
||||
"webpack": "^5.0.0-beta.7",
|
||||
"typedoc": "^0.15.6",
|
||||
"typescript": "^3.7.3",
|
||||
"url-loader": "^3.0.0",
|
||||
"val-loader": "2.1.0",
|
||||
"webpack": "^5.0.0-beta.11",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"yaml-loader": "0.5.0"
|
||||
},
|
||||
@@ -133,8 +136,5 @@
|
||||
"lint": "eslint --ext ts */src",
|
||||
"postinstall": "node ./scripts/install-deps.js"
|
||||
},
|
||||
"repository": "eugeny/terminus",
|
||||
"dependencies": {
|
||||
"eslint-plugin-import": "^2.18.2"
|
||||
}
|
||||
"repository": "eugeny/terminus"
|
||||
}
|
||||
|
@@ -25,8 +25,5 @@ if (['darwin', 'linux'].includes(process.platform)) {
|
||||
for (let x of vars.builtinPlugins) {
|
||||
sh.ln('-fs', '../' + x, x)
|
||||
}
|
||||
for (let x of vars.bundledModules) {
|
||||
sh.ln('-fs', '../app/node_modules/' + x, x)
|
||||
}
|
||||
sh.cd('..')
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-community-color-schemes",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Community color schemes for Terminus",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-core",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Terminus core",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
|
@@ -47,8 +47,11 @@ title-bar(
|
||||
(click)='item.click()',
|
||||
ngbDropdownItem,
|
||||
)
|
||||
.icon-wrapper([innerHTML]='sanitizeIcon(item.icon)')
|
||||
.ml-3 {{item.title}}
|
||||
.icon-wrapper(
|
||||
*ngIf='hasIcons(button.submenuItems)',
|
||||
[innerHTML]='sanitizeIcon(item.icon)'
|
||||
)
|
||||
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||
|
||||
.drag-space.background([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
||||
|
||||
@@ -70,8 +73,11 @@ title-bar(
|
||||
(click)='item.click()',
|
||||
ngbDropdownItem,
|
||||
)
|
||||
.icon-wrapper([innerHTML]='sanitizeIcon(item.icon)')
|
||||
.ml-3 {{item.title}}
|
||||
.icon-wrapper(
|
||||
*ngIf='hasIcons(button.submenuItems)',
|
||||
[innerHTML]='sanitizeIcon(item.icon)'
|
||||
)
|
||||
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||
|
||||
button.btn.btn-secondary.btn-tab-bar.btn-update(
|
||||
*ngIf='updatesAvailable',
|
||||
|
@@ -250,6 +250,10 @@ export class AppRootComponent {
|
||||
}
|
||||
}
|
||||
|
||||
hasIcons (submenuItems: ToolbarButton[]): boolean {
|
||||
return submenuItems.some(x => !!x.icon)
|
||||
}
|
||||
|
||||
sanitizeIcon (icon: string): any {
|
||||
return this.domSanitizer.bypassSecurityTrustHtml(icon || '')
|
||||
}
|
||||
|
@@ -1,4 +0,0 @@
|
||||
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
||||
i.fas.fa-square.off
|
||||
i.fas.fa-check-square.on
|
||||
.text {{text}}
|
@@ -1,55 +0,0 @@
|
||||
:host {
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255,255,255,.05);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(255,255,255,.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.off {
|
||||
color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -2px;
|
||||
transition: 0.25s opacity;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
i.on, &.active i.off {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
i.off, &.active i.on {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
flex: auto;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
@@ -4,8 +4,12 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'checkbox',
|
||||
template: require('./checkbox.component.pug'),
|
||||
styles: [require('./checkbox.component.scss')],
|
||||
template: `
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" [(ngModel)]='model'>
|
||||
<label class="custom-control-label">{{text}}</label>
|
||||
</div>
|
||||
`,
|
||||
providers: [
|
||||
{ provide: NG_VALUE_ACCESSOR, useExisting: CheckboxComponent, multi: true },
|
||||
],
|
||||
|
@@ -48,7 +48,7 @@ $tabs-height: 38px;
|
||||
width: $button-size;
|
||||
height: $button-size;
|
||||
border-radius: $button-size / 2;
|
||||
line-height: $button-size * 0.9;
|
||||
line-height: $button-size;
|
||||
align-self: center;
|
||||
margin-right: 10px;
|
||||
|
||||
|
@@ -16,55 +16,8 @@
|
||||
padding-left: 10px;
|
||||
margin-left: -10px;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255,255,255,.05);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.body {
|
||||
$border-width: 2px;
|
||||
border-radius: 5px;
|
||||
border: $border-width solid rgba(255, 255, 255, .2);
|
||||
padding: $padding;
|
||||
height: $toggle-size + $border-width * 2 + $padding * 2;
|
||||
width: $toggle-size * 2 + $border-width * 2 + $padding * 2;
|
||||
|
||||
position: relative;
|
||||
|
||||
.toggle {
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
width: $toggle-size;
|
||||
height: $toggle-size;
|
||||
background: #475158;
|
||||
top: $padding;
|
||||
left: $padding;
|
||||
transition: 0.25s left;
|
||||
line-height: 19px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
|
||||
i {
|
||||
opacity: 0;
|
||||
transition: 0.25s opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active .body .toggle {
|
||||
left: $toggle-size + $padding;
|
||||
|
||||
i {
|
||||
color: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(255,255,255,.1);
|
||||
}
|
||||
}
|
||||
|
@@ -6,13 +6,10 @@ import { CheckboxComponent } from './checkbox.component'
|
||||
@Component({
|
||||
selector: 'toggle',
|
||||
template: `
|
||||
<div class="switch">
|
||||
<div class="body">
|
||||
<div class="toggle" [class.bg-primary]='model'>
|
||||
<i class="fa fa-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" [(ngModel)]='model'>
|
||||
<label class="custom-control-label"></label>
|
||||
</div>
|
||||
`,
|
||||
styles: [require('./toggle.component.scss')],
|
||||
providers: [
|
||||
|
@@ -78,7 +78,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
return [
|
||||
{
|
||||
label: 'Rename',
|
||||
click: () => this.zone.run(() => tabHeader && tabHeader.showRenameTabModal()),
|
||||
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
|
@@ -1,103 +1,11 @@
|
||||
$tab-border-radius: 5px;
|
||||
@import "./theme.vars";
|
||||
|
||||
// ---------
|
||||
|
||||
|
||||
$button-hover-bg: rgba(0, 0, 0, .25);
|
||||
$button-active-bg: rgba(0, 0, 0, .5);
|
||||
|
||||
|
||||
$white: #fff !default;
|
||||
$black: #000 !default;
|
||||
$red: #d9534f !default;
|
||||
$orange: #f0ad4e !default;
|
||||
$yellow: #ffd500 !default;
|
||||
$green: #5cb85c !default;
|
||||
$blue: #0275d8 !default;
|
||||
$teal: #5bc0de !default;
|
||||
$pink: #ff5b77 !default;
|
||||
$purple: #613d7c !default;
|
||||
|
||||
$theme-colors: (
|
||||
"primary": $blue,
|
||||
"secondary": #394b5d
|
||||
);
|
||||
|
||||
$content-bg: rgba(39, 49, 60, 0.65); //#1D272D;
|
||||
$content-bg-solid: #1D272D;
|
||||
$body-bg: #131d27;
|
||||
$body-bg2: #20333e;
|
||||
|
||||
$body-color: #ccc;
|
||||
$font-family-sans-serif: "Source Sans Pro";
|
||||
$font-family-monospace: "Source Code Pro";
|
||||
$font-size-base: 14rem / 16;
|
||||
|
||||
$btn-border-radius: 0;
|
||||
$btn-secondary-color: #ccc;
|
||||
$btn-secondary-bg: #222;
|
||||
$btn-secondary-border: #444;
|
||||
|
||||
//$btn-warning-bg: rgba($orange, .5);
|
||||
|
||||
|
||||
$nav-tabs-border-width: 0;
|
||||
$nav-tabs-border-radius: 0;
|
||||
$nav-tabs-link-hover-border-color: $body-bg;
|
||||
$nav-tabs-active-link-hover-color: $white;
|
||||
$nav-tabs-active-link-hover-bg: $blue;
|
||||
$nav-tabs-active-link-hover-border-color: darken($blue, 30%);
|
||||
$nav-pills-border-radius: 0;
|
||||
|
||||
$input-bg: #111;
|
||||
$input-disabled-bg: #333;
|
||||
|
||||
$input-color: $body-color;
|
||||
$input-color-placeholder: #333;
|
||||
$input-border-color: #344;
|
||||
$input-border-width: 1px;
|
||||
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
|
||||
$input-border-radius: 0;
|
||||
$custom-select-border-radius: 0;
|
||||
$input-bg-focus: $input-bg;
|
||||
$input-border-focus: lighten($blue, 25%);
|
||||
$input-focus-box-shadow: none;
|
||||
$input-color-focus: $input-color;
|
||||
$input-group-addon-bg: $body-bg;
|
||||
$input-group-addon-border-color: $input-border-color;
|
||||
|
||||
$modal-content-bg: $content-bg-solid;
|
||||
$modal-content-border-color: $body-bg;
|
||||
$modal-header-border-color: transparent;
|
||||
$modal-footer-border-color: transparent;
|
||||
|
||||
$popover-bg: $body-bg;
|
||||
|
||||
$dropdown-bg: $body-bg;
|
||||
$dropdown-link-color: $body-color;
|
||||
$dropdown-link-hover-color: white;
|
||||
$dropdown-link-hover-bg: $body-bg2;
|
||||
//$dropdown-link-active-color: $component-active-color;
|
||||
//$dropdown-link-active-bg: $component-active-bg;
|
||||
$dropdown-link-disabled-color: #333;
|
||||
$dropdown-header-color: #333;
|
||||
|
||||
$list-group-color: $body-color;
|
||||
$list-group-bg: rgba(255,255,255,.05);
|
||||
$list-group-border-color: rgba(255,255,255,.1);
|
||||
$list-group-hover-bg: rgba(255,255,255,.1);
|
||||
$list-group-link-active-bg: rgba(255,255,255,.2);
|
||||
|
||||
$list-group-action-color: $body-color;
|
||||
$list-group-action-bg: rgba(255,255,255,.05);
|
||||
$list-group-action-active-bg: $list-group-link-active-bg;
|
||||
|
||||
$pre-bg: $dropdown-bg;
|
||||
$pre-color: $dropdown-link-color;
|
||||
|
||||
$alert-danger-bg: $body-bg;
|
||||
$alert-danger-text: $red;
|
||||
$alert-danger-border: $red;
|
||||
|
||||
$headings-font-weight: lighter;
|
||||
$headings-color: #eee;
|
||||
|
||||
@import '~bootstrap/scss/bootstrap.scss';
|
||||
|
||||
window-controls {
|
||||
@@ -236,13 +144,14 @@ settings-tab > ngb-tabset {
|
||||
border: none;
|
||||
padding: 10px 50px 10px 20px;
|
||||
font-size: 14px;
|
||||
border-radius: 0;
|
||||
|
||||
&:not(.active) {
|
||||
color: $body-color;
|
||||
color: $body-color;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,14 +219,6 @@ hotkey-input-modal {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
background: $btn-secondary-bg;
|
||||
.nav-link {
|
||||
transition: 0.25s all;
|
||||
border-bottom-color: $nav-tabs-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
ngb-tabset .tab-content {
|
||||
padding-top: 20px;
|
||||
}
|
||||
@@ -361,22 +262,10 @@ ngb-tabset .tab-content {
|
||||
}
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'><path fill='#444' d='M7.406 7.828l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'></path></svg>");
|
||||
background-position: 100% 50%;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
checkbox i.on {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
toggle.active .body .toggle {
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
.modal .modal-footer {
|
||||
background: rgba(0, 0, 0, .25);
|
||||
|
||||
@@ -405,3 +294,101 @@ toggle.active .body .toggle {
|
||||
*::-webkit-resizer {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
search-panel {
|
||||
background: rgba(39, 49, 60, 0.65) !important;
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btn.btn-outline-secondary {
|
||||
@include button-outline-variant(#9badb9, #fff);
|
||||
&:hover:not([disabled]), &:active:not([disabled]), &.active:not([disabled]) {
|
||||
background-color: #3f484e;
|
||||
border-color: darken(#9badb9, 25%);
|
||||
}
|
||||
|
||||
border-color: darken(#9badb9, 25%);
|
||||
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
color: #9badb9;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-warning:not(:disabled):not(.disabled) {
|
||||
&.active, &:active {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-secondary:not(:disabled):not(.disabled) {
|
||||
&.active, &:active {
|
||||
background: #191e23;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
&:hover, &[aria-expanded=true], &:active, &.active {
|
||||
color: $link-hover-color;
|
||||
border-radius: $btn-border-radius;
|
||||
}
|
||||
|
||||
&[aria-expanded=true], &:active, &.active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group .btn.active {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.nav-justified .nav-link {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
border: none;
|
||||
border-bottom: $nav-tabs-border-width solid transparent;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
padding: 5px 0;
|
||||
margin-right: 20px;
|
||||
|
||||
uib-tab-heading > i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
@include hover-focus {
|
||||
color: $nav-tabs-link-active-color;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $nav-link-disabled-color;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item:last-child .nav-link {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: $nav-tabs-link-active-color;
|
||||
border-color: $nav-tabs-link-active-border-color;
|
||||
}
|
||||
}
|
||||
|
187
terminus-core/src/theme.vars.scss
Normal file
187
terminus-core/src/theme.vars.scss
Normal file
@@ -0,0 +1,187 @@
|
||||
$white: #fff;
|
||||
$gray-100: #f8f9fa;
|
||||
$gray-200: #e9ecef;
|
||||
$gray-300: #dee2e6;
|
||||
$gray-400: #ced4da;
|
||||
$gray-500: #adb5bd;
|
||||
$gray-600: #6c757d;
|
||||
$gray-700: #495057;
|
||||
$gray-800: #343a40;
|
||||
$gray-900: #212529;
|
||||
$black: #000;
|
||||
|
||||
|
||||
$red: #d9534f !default;
|
||||
$orange: #f0ad4e !default;
|
||||
$yellow: #ffd500 !default;
|
||||
$green: #5cb85c !default;
|
||||
$blue: #0275d8 !default;
|
||||
$teal: #5bc0de !default;
|
||||
$pink: #ff5b77 !default;
|
||||
$purple: #613d7c !default;
|
||||
|
||||
|
||||
@import "~bootstrap/scss/functions";
|
||||
|
||||
$content-bg: rgba(39, 49, 60, 0.65); //#1D272D;
|
||||
$content-bg-solid: #1D272D;
|
||||
|
||||
$table-bg: rgba(255,255,255,.05);
|
||||
$table-bg-hover: rgba(255,255,255,.1);
|
||||
$table-border-color: rgba(255,255,255,.1);
|
||||
|
||||
$theme-colors: (
|
||||
primary: $blue,
|
||||
secondary: #38434e,
|
||||
success: $green,
|
||||
info: $blue,
|
||||
warning: $orange,
|
||||
danger: $red,
|
||||
light: $gray-300,
|
||||
dark: $gray-800,
|
||||
rare: $purple
|
||||
);
|
||||
|
||||
$body-color: #ccc;
|
||||
$body-bg: #131d27;
|
||||
$body-bg2: #20333e;
|
||||
|
||||
|
||||
$font-family-sans-serif: "Source Sans Pro";
|
||||
$font-family-monospace: "Source Code Pro";
|
||||
$font-size-base: 14rem / 16;
|
||||
$font-size-lg: 1.28rem;
|
||||
$font-size-sm: .85rem;
|
||||
|
||||
$line-height-base: 1.6;
|
||||
|
||||
$headings-color: #ced9e2;
|
||||
$headings-font-weight: lighter;
|
||||
|
||||
$input-btn-padding-y: .3rem;
|
||||
$input-btn-padding-x: .9rem;
|
||||
$input-btn-line-height: 1.6;
|
||||
$input-btn-line-height-sm: 1.8;
|
||||
$input-btn-line-height-lg: 1.8;
|
||||
|
||||
$btn-link-disabled-color: $gray-600;
|
||||
$btn-focus-box-shadow: none;
|
||||
|
||||
$h4-font-size: 18px;
|
||||
|
||||
$link-color: $gray-400;
|
||||
$link-hover-color: $white;
|
||||
$link-hover-decoration: none;
|
||||
|
||||
$component-active-color: $white;
|
||||
$component-active-bg: #2f3a42;
|
||||
|
||||
$list-group-bg: $table-bg;
|
||||
$list-group-border-color: $table-border-color;
|
||||
|
||||
$list-group-item-padding-y: 0.8rem;
|
||||
$list-group-item-padding-x: 1rem;
|
||||
|
||||
$list-group-hover-bg: $table-bg-hover;
|
||||
$list-group-active-bg: rgba(255,255,255,.2);
|
||||
$list-group-active-color: $component-active-color;
|
||||
$list-group-active-border-color: translate;
|
||||
|
||||
$list-group-action-color: $body-color;
|
||||
$list-group-action-hover-color: white;
|
||||
|
||||
$list-group-action-active-color: $component-active-color;
|
||||
$list-group-action-active-bg: $list-group-active-bg;
|
||||
|
||||
$alert-padding-y: 0.9rem;
|
||||
$alert-padding-x: 1.25rem;
|
||||
|
||||
$input-box-shadow: none;
|
||||
|
||||
$transition-base: all .15s ease-in-out;
|
||||
$transition-fade: opacity .1s linear;
|
||||
$transition-collapse: height .35s ease;
|
||||
$btn-transition: all .15s ease-in-out;
|
||||
|
||||
$popover-bg: $body-bg;
|
||||
$popover-body-color: $body-color;
|
||||
$popover-header-bg: $table-bg-hover;
|
||||
$popover-header-color: $headings-color;
|
||||
$popover-arrow-color: $popover-bg;
|
||||
$popover-max-width: 360px;
|
||||
|
||||
$btn-border-width: 2px;
|
||||
|
||||
$input-bg: #181e23;
|
||||
$input-disabled-bg: #2e3235;
|
||||
|
||||
$input-color: #ddd;
|
||||
$input-border-color: $input-bg;
|
||||
$input-border-width: 2px;
|
||||
|
||||
$input-focus-bg: $input-bg;
|
||||
$input-focus-border-color: rgba(171, 171, 171, 0.61);
|
||||
$input-focus-color: $input-color;
|
||||
|
||||
$input-btn-focus-color: var(--focus-color);
|
||||
$input-btn-focus-box-shadow: 0 0 0 2px $input-btn-focus-color;
|
||||
|
||||
$input-group-addon-color: $input-color;
|
||||
$input-group-addon-bg: $input-bg;
|
||||
$input-group-addon-border-color: transparent;
|
||||
$input-group-btn-border-color: $input-bg;
|
||||
|
||||
$nav-tabs-border-radius: 0;
|
||||
$nav-tabs-border-color: transparent;
|
||||
$nav-tabs-border-width: 2px;
|
||||
$nav-tabs-link-hover-border-color: transparent;
|
||||
$nav-tabs-link-active-color: #eee;
|
||||
$nav-tabs-link-active-bg: transparent;
|
||||
$nav-tabs-link-active-border-color: #eee;
|
||||
|
||||
$navbar-padding-y: 0;
|
||||
$navbar-padding-x: 0;
|
||||
|
||||
$dropdown-bg: $table-bg;
|
||||
$dropdown-color: $body-color;
|
||||
$dropdown-border-width: 1px;
|
||||
$dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175);
|
||||
$dropdown-header-color: $gray-500;
|
||||
|
||||
$dropdown-link-color: $body-color;
|
||||
$dropdown-link-hover-color: #eee;
|
||||
$dropdown-link-hover-bg: rgba(255,255,255,.04);
|
||||
$dropdown-link-active-color: white;
|
||||
$dropdown-link-active-bg: rgba(0, 0, 0, .2);
|
||||
$dropdown-item-padding-y: 0.5rem;
|
||||
$dropdown-item-padding-x: 1.5rem;
|
||||
|
||||
|
||||
$code-color: $orange;
|
||||
$code-bg: rgba(0, 0, 0, .25);
|
||||
$code-padding-y: 3px;
|
||||
$code-padding-x: 5px;
|
||||
$pre-bg: $dropdown-bg;
|
||||
$pre-color: $dropdown-link-color;
|
||||
|
||||
$badge-font-size: 0.75rem;
|
||||
$badge-font-weight: bold;
|
||||
$badge-padding-y: 4px;
|
||||
$badge-padding-x: 6px;
|
||||
|
||||
|
||||
$custom-control-indicator-size: 1.2rem;
|
||||
$custom-control-indicator-bg: $body-bg;
|
||||
$custom-control-indicator-border-color: lighten($body-bg, 25%);
|
||||
$custom-control-indicator-checked-bg: theme-color("primary");
|
||||
$custom-control-indicator-checked-color: $body-bg;
|
||||
$custom-control-indicator-checked-border-color: transparent;
|
||||
$custom-control-indicator-active-bg: rgba(255, 255, 0, 0.5);
|
||||
|
||||
|
||||
$modal-content-bg: $content-bg-solid;
|
||||
$modal-content-border-color: $body-bg;
|
||||
$modal-header-border-width: 0;
|
||||
$modal-footer-border-color: #222;
|
||||
$modal-footer-border-width: 1px;
|
||||
$modal-content-border-width: 0;
|
@@ -118,9 +118,9 @@ colorspace@1.1.x:
|
||||
text-hex "1.0.x"
|
||||
|
||||
core-js@^3.1.2:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.2.tgz#ee2b1a60b50388d8ddcda8cdb44a92c7a9ea76df"
|
||||
integrity sha512-bUTfqFWtNKWp73oNIfRkqwYZJeNT3lstzZcAkhhiuvDraRSgOH1/+F9ZklbpR4zpdKuo4cpXN8tKP7s61yjX+g==
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.1.tgz#39d5e2e346258cc01eb7d44345b1c3c014ca3f05"
|
||||
integrity sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-plugin-manager",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Terminus' plugin manager",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/semver": "^6.0.0",
|
||||
"axios": "^0.19.0",
|
||||
"mz": "^2.6.0",
|
||||
"semver": "^6.1.0"
|
||||
"semver": "^7.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^7",
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
.d-flex
|
||||
h3.mb-1 Installed
|
||||
button.btn.btn-outline-info.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||
button.btn.btn-outline-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||
i.fas.fa-folder
|
||||
span Plugins folder
|
||||
|
||||
@@ -28,19 +28,19 @@
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
||||
|
||||
button.btn.btn-primary.ml-2(
|
||||
button.btn.btn-link.text-primary.ml-2(
|
||||
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='enablePlugin(plugin)'
|
||||
)
|
||||
i.fas.fa-fw.fa-play
|
||||
|
||||
button.btn.btn-secondary.ml-2(
|
||||
button.btn.btn-link.ml-2(
|
||||
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='disablePlugin(plugin)'
|
||||
)
|
||||
i.fas.fa-fw.fa-pause
|
||||
|
||||
button.btn.btn-danger.ml-2(
|
||||
button.btn.btn-link.text-danger.ml-2(
|
||||
(click)='uninstallPlugin(plugin)',
|
||||
*ngIf='!plugin.isBuiltin',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
|
@@ -58,10 +58,10 @@ object-assign@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
semver@^6.1.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
semver@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667"
|
||||
integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==
|
||||
|
||||
thenify-all@^1.0.0:
|
||||
version "1.6.0"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-settings",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Terminus terminal settings page",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-ssh",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "SSH connection manager for Terminus",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
@@ -20,7 +20,8 @@
|
||||
"@types/node": "12.7.3",
|
||||
"@types/ssh2": "^0.5.35",
|
||||
"ssh2": "^0.8.2",
|
||||
"ssh2-streams": "^0.4.2"
|
||||
"ssh2-streams": "^0.4.2",
|
||||
"terminus-terminal": "^1.0.98-nightly.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^7",
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
import { Server, Socket, createServer, createConnection } from 'net'
|
||||
import { Client, ClientChannel } from 'ssh2'
|
||||
import { Logger } from 'terminus-core'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
|
||||
export interface LoginScript {
|
||||
expect: string
|
||||
@@ -21,7 +25,7 @@ export interface SSHConnection {
|
||||
user: string
|
||||
password?: string
|
||||
privateKey?: string
|
||||
group?: string
|
||||
group: string | null
|
||||
scripts?: LoginScript[]
|
||||
keepaliveInterval?: number
|
||||
keepaliveCountMax?: number
|
||||
@@ -30,18 +34,82 @@ export interface SSHConnection {
|
||||
algorithms?: {[t: string]: string[]}
|
||||
}
|
||||
|
||||
export enum PortForwardType {
|
||||
Local, Remote
|
||||
}
|
||||
|
||||
export class ForwardedPort {
|
||||
type: PortForwardType
|
||||
host = '127.0.0.1'
|
||||
port: number
|
||||
targetAddress: string
|
||||
targetPort: number
|
||||
|
||||
private listener: Server
|
||||
|
||||
async startLocalListener (callback: (Socket) => void): Promise<void> {
|
||||
this.listener = createServer(callback)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.listener.listen(this.port, '127.0.0.1')
|
||||
this.listener.on('error', reject)
|
||||
this.listener.on('listening', resolve)
|
||||
})
|
||||
}
|
||||
|
||||
stopLocalListener () {
|
||||
this.listener.close()
|
||||
}
|
||||
|
||||
toString () {
|
||||
if (this.type === PortForwardType.Local) {
|
||||
return `(local) ${this.host}:${this.port} → (remote) ${this.targetAddress}:${this.targetPort}`
|
||||
} else {
|
||||
return `(remote) ${this.host}:${this.port} → (local) ${this.targetAddress}:${this.targetPort}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SSHSession extends BaseSession {
|
||||
scripts?: LoginScript[]
|
||||
shell: any
|
||||
shell: ClientChannel
|
||||
ssh: Client
|
||||
forwardedPorts: ForwardedPort[] = []
|
||||
logger: Logger
|
||||
|
||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||
private serviceMessage = new Subject<string>()
|
||||
|
||||
constructor (public connection: SSHConnection) {
|
||||
super()
|
||||
this.scripts = connection.scripts || []
|
||||
}
|
||||
|
||||
start () {
|
||||
async start () {
|
||||
this.open = true
|
||||
|
||||
try {
|
||||
try {
|
||||
this.shell = await this.openShellChannel({ x11: true })
|
||||
} catch (e) {
|
||||
if (e.toString().includes('Unable to request X11')) {
|
||||
this.logger.debug('X11 forwarding rejected, trying without')
|
||||
this.shell = await this.openShellChannel({})
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.emitServiceMessage(`Remote rejected opening a shell channel: ${err}`)
|
||||
}
|
||||
|
||||
this.shell.on('greeting', greeting => {
|
||||
this.emitServiceMessage(`Shell greeting: ${greeting}`)
|
||||
})
|
||||
|
||||
this.shell.on('banner', banner => {
|
||||
this.emitServiceMessage(`Shell banner: ${banner}`)
|
||||
})
|
||||
|
||||
this.shell.on('data', data => {
|
||||
const dataString = data.toString()
|
||||
this.emitOutput(dataString)
|
||||
@@ -67,12 +135,12 @@ export class SSHSession extends BaseSession {
|
||||
}
|
||||
|
||||
if (match) {
|
||||
console.log('Executing script: "' + cmd + '"')
|
||||
this.logger.info('Executing script: "' + cmd + '"')
|
||||
this.shell.write(cmd + '\n')
|
||||
this.scripts = this.scripts.filter(x => x !== script)
|
||||
} else {
|
||||
if (script.optional) {
|
||||
console.log('Skip optional script: ' + script.expect)
|
||||
this.logger.debug('Skip optional script: ' + script.expect)
|
||||
found = true
|
||||
this.scripts = this.scripts.filter(x => x !== script)
|
||||
} else {
|
||||
@@ -88,17 +156,140 @@ export class SSHSession extends BaseSession {
|
||||
})
|
||||
|
||||
this.shell.on('end', () => {
|
||||
this.logger.info('Shell session ended')
|
||||
if (this.open) {
|
||||
this.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
this.ssh.on('tcp connection', (details, accept, reject) => {
|
||||
this.logger.info(`Incoming forwarded connection: (remote) ${details.srcIP}:${details.srcPort} -> (local) ${details.destIP}:${details.destPort}`)
|
||||
const forward = this.forwardedPorts.find(x => x.port === details.destPort)
|
||||
if (!forward) {
|
||||
this.emitServiceMessage(`Rejected incoming forwarded connection for unrecognized port ${details.destPort}`)
|
||||
return reject()
|
||||
}
|
||||
const socket = new Socket()
|
||||
socket.connect(forward.targetPort, forward.targetAddress)
|
||||
socket.on('error', e => {
|
||||
this.emitServiceMessage(`Could not forward the remote connection to ${forward.targetAddress}:${forward.targetPort}: ${e}`)
|
||||
reject()
|
||||
})
|
||||
socket.on('connect', () => {
|
||||
this.logger.info('Connection forwarded')
|
||||
const stream = accept()
|
||||
stream.pipe(socket)
|
||||
socket.pipe(stream)
|
||||
stream.on('close', () => {
|
||||
socket.destroy()
|
||||
})
|
||||
socket.on('close', () => {
|
||||
stream.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.ssh.on('x11', (details, accept, reject) => {
|
||||
this.logger.info(`Incoming X11 connection from ${details.srcIP}:${details.srcPort}`)
|
||||
let displaySpec = process.env.DISPLAY || ':0'
|
||||
this.logger.debug(`Trying display ${displaySpec}`)
|
||||
let xHost = displaySpec.split(':')[0]
|
||||
let xDisplay = parseInt(displaySpec.split(':')[1].split('.')[0] || '0')
|
||||
let xPort = xDisplay < 100 ? xDisplay + 6000 : xDisplay
|
||||
|
||||
const socket = displaySpec.startsWith('/') ? createConnection(displaySpec) : new Socket()
|
||||
if (!displaySpec.startsWith('/')) {
|
||||
socket.connect(xPort, xHost)
|
||||
}
|
||||
socket.on('error', e => {
|
||||
this.emitServiceMessage(`Could not connect to the X server ${xHost}:${xPort}: ${e}`)
|
||||
reject()
|
||||
})
|
||||
socket.on('connect', () => {
|
||||
this.logger.info('Connection forwarded')
|
||||
const stream = accept()
|
||||
stream.pipe(socket)
|
||||
socket.pipe(stream)
|
||||
stream.on('close', () => {
|
||||
socket.destroy()
|
||||
})
|
||||
socket.on('close', () => {
|
||||
stream.close()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
this.executeUnconditionalScripts()
|
||||
}
|
||||
|
||||
emitServiceMessage (msg: string) {
|
||||
this.serviceMessage.next(msg)
|
||||
this.logger.info(msg)
|
||||
}
|
||||
|
||||
async addPortForward (fw: ForwardedPort) {
|
||||
if (fw.type === PortForwardType.Local) {
|
||||
await fw.startLocalListener((socket: Socket) => {
|
||||
this.logger.info(`New connection on ${fw}`)
|
||||
this.ssh.forwardOut(
|
||||
socket.remoteAddress || '127.0.0.1',
|
||||
socket.remotePort || 0,
|
||||
fw.targetAddress,
|
||||
fw.targetPort,
|
||||
(err, stream) => {
|
||||
if (err) {
|
||||
this.emitServiceMessage(`Remote has rejected the forwaded connection via ${fw}: ${err}`)
|
||||
socket.destroy()
|
||||
return
|
||||
}
|
||||
stream.pipe(socket)
|
||||
socket.pipe(stream)
|
||||
stream.on('close', () => {
|
||||
socket.destroy()
|
||||
})
|
||||
socket.on('close', () => {
|
||||
stream.close()
|
||||
})
|
||||
}
|
||||
)
|
||||
}).then(() => {
|
||||
this.emitServiceMessage(`Forwaded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}).catch(e => {
|
||||
this.emitServiceMessage(`Failed to forward port ${fw}: ${e}`)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
if (fw.type === PortForwardType.Remote) {
|
||||
await new Promise((resolve, reject) => {
|
||||
this.ssh.forwardIn(fw.host, fw.port, err => {
|
||||
if (err) {
|
||||
this.emitServiceMessage(`Remote rejected port forwarding for ${fw}: ${err}`)
|
||||
return reject(err)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
this.emitServiceMessage(`Forwaded ${fw}`)
|
||||
this.forwardedPorts.push(fw)
|
||||
}
|
||||
}
|
||||
|
||||
async removePortForward (fw: ForwardedPort) {
|
||||
if (fw.type === PortForwardType.Local) {
|
||||
fw.stopLocalListener()
|
||||
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
|
||||
}
|
||||
if (fw.type === PortForwardType.Remote) {
|
||||
this.ssh.unforwardIn(fw.host, fw.port)
|
||||
this.forwardedPorts = this.forwardedPorts.filter(x => x !== fw)
|
||||
}
|
||||
this.emitServiceMessage(`Stopped forwarding ${fw}`)
|
||||
}
|
||||
|
||||
resize (columns, rows) {
|
||||
if (this.shell) {
|
||||
this.shell.setWindow(rows, columns)
|
||||
this.shell.setWindow(rows, columns, rows, columns)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +305,11 @@ export class SSHSession extends BaseSession {
|
||||
}
|
||||
}
|
||||
|
||||
async destroy (): Promise<void> {
|
||||
this.serviceMessage.complete()
|
||||
await super.destroy()
|
||||
}
|
||||
|
||||
async getChildProcesses (): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
@@ -126,6 +322,18 @@ export class SSHSession extends BaseSession {
|
||||
return null
|
||||
}
|
||||
|
||||
private openShellChannel (options): Promise<ClientChannel> {
|
||||
return new Promise<ClientChannel>((resolve, reject) => {
|
||||
this.ssh.shell({ term: 'xterm-256color' }, options, (err, shell) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(shell)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private executeUnconditionalScripts () {
|
||||
if (this.scripts) {
|
||||
for (const script of this.scripts) {
|
||||
|
@@ -1,69 +1,69 @@
|
||||
.modal-body
|
||||
ngb-tabset(type='pills', [activeId]='basic')
|
||||
ngb-tabset([activeId]='basic')
|
||||
ngb-tab(id='basic')
|
||||
ng-template(ngbTabTitle) General
|
||||
ng-template(ngbTabContent)
|
||||
.form-group
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='connection.name',
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='connection.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Group
|
||||
input.form-control(
|
||||
type='text',
|
||||
type='text',
|
||||
placeholder='Ungrouped',
|
||||
[(ngModel)]='connection.group',
|
||||
[(ngModel)]='connection.group',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Host
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='connection.host',
|
||||
type='text',
|
||||
[(ngModel)]='connection.host',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Port
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='22',
|
||||
[(ngModel)]='connection.port',
|
||||
placeholder='22',
|
||||
[(ngModel)]='connection.port',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Username
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='connection.user',
|
||||
type='text',
|
||||
[(ngModel)]='connection.user',
|
||||
)
|
||||
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Password
|
||||
.description(*ngIf='!hasSavedPassword') Save a password in the keychain
|
||||
.description(*ngIf='hasSavedPassword') There is a saved password for this connection
|
||||
button.btn.btn-outline-success.ml-4(*ngIf='!hasSavedPassword', (click)='setPassword()')
|
||||
i.fas.fa-key
|
||||
button.btn.btn-outline-success.ml-4(*ngIf='!hasSavedPassword', (click)='setPassword()')
|
||||
i.fas.fa-key
|
||||
span Set password
|
||||
button.btn.btn-danger.ml-4(*ngIf='hasSavedPassword', (click)='clearSavedPassword()')
|
||||
button.btn.btn-danger.ml-4(*ngIf='hasSavedPassword', (click)='clearSavedPassword()')
|
||||
i.fas.fa-trash-alt
|
||||
span Forget
|
||||
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Private key
|
||||
.title Private key
|
||||
.description Path to the private key file
|
||||
.input-group
|
||||
input.form-control(
|
||||
type='text',
|
||||
placeholder='Key file path',
|
||||
type='text',
|
||||
placeholder='Key file path',
|
||||
[(ngModel)]='connection.privateKey'
|
||||
)
|
||||
.input-group-btn
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='selectPrivateKey()')
|
||||
i.fas.fa-folder-open
|
||||
|
||||
@@ -73,27 +73,27 @@
|
||||
.form-group
|
||||
label Keep Alive Interval (Milliseconds)
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='0',
|
||||
[(ngModel)]='connection.keepaliveInterval',
|
||||
type='number',
|
||||
placeholder='0',
|
||||
[(ngModel)]='connection.keepaliveInterval',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Max Keep Alive Count
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='3',
|
||||
[(ngModel)]='connection.keepaliveCountMax',
|
||||
type='number',
|
||||
placeholder='3',
|
||||
[(ngModel)]='connection.keepaliveCountMax',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Ready Timeout (Milliseconds)
|
||||
input.form-control(
|
||||
type='number',
|
||||
placeholder='20000',
|
||||
[(ngModel)]='connection.readyTimeout',
|
||||
type='number',
|
||||
placeholder='20000',
|
||||
[(ngModel)]='connection.readyTimeout',
|
||||
)
|
||||
|
||||
|
||||
.form-group
|
||||
label Ciphers
|
||||
div(*ngFor='let alg of supportedAlgorithms.cipher')
|
||||
@@ -128,12 +128,12 @@
|
||||
tr(*ngFor='let script of connection.scripts')
|
||||
td.pr-2
|
||||
input.form-control(
|
||||
type='text',
|
||||
type='text',
|
||||
[(ngModel)]='script.expect'
|
||||
)
|
||||
td
|
||||
input.form-control(
|
||||
type='text',
|
||||
type='text',
|
||||
[(ngModel)]='script.send'
|
||||
)
|
||||
td.pl-2
|
||||
@@ -152,11 +152,11 @@
|
||||
i.fas.fa-arrow-down
|
||||
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||
i.fas.fa-trash
|
||||
|
||||
|
||||
button.btn.btn-outline-info.mt-2((click)='addScript()')
|
||||
i.fas.fa-plus
|
||||
span New item
|
||||
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-outline-primary((click)='save()') Save
|
||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
||||
|
@@ -66,7 +66,7 @@ export class EditConnectionModalComponent {
|
||||
modal.componentInstance.password = true
|
||||
try {
|
||||
const result = await modal.result
|
||||
if (result && result.value) {
|
||||
if (result?.value) {
|
||||
this.passwordStorage.savePassword(this.connection, result.value)
|
||||
this.hasSavedPassword = true
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ export class SSHModalComponent {
|
||||
|
||||
const connection: SSHConnection = {
|
||||
name: this.quickTarget,
|
||||
group: null,
|
||||
host,
|
||||
user,
|
||||
port,
|
||||
@@ -91,7 +92,7 @@ export class SSHModalComponent {
|
||||
}
|
||||
|
||||
for (const connection of connections) {
|
||||
connection.group = connection.group || undefined
|
||||
connection.group = connection.group || null
|
||||
let group = this.childGroups.find(x => x.name === connection.group)
|
||||
if (!group) {
|
||||
group = {
|
||||
|
@@ -0,0 +1,48 @@
|
||||
.modal-header
|
||||
h5.m-0 Port forwarding
|
||||
|
||||
.modal-body.pt-0
|
||||
.list-group-light.mb-3
|
||||
.list-group-item.d-flex.align-items-center(*ngFor='let fw of session.forwardedPorts')
|
||||
strong(*ngIf='fw.type === PortForwardType.Local') Local
|
||||
strong(*ngIf='fw.type === PortForwardType.Remote') Remote
|
||||
.ml-3 {{fw.host}}:{{fw.port}} → {{fw.targetAddress}}:{{fw.targetPort}}
|
||||
button.btn.btn-link.ml-auto((click)='remove(fw)')
|
||||
i.fas.fa-trash-alt.mr-2
|
||||
span Remove
|
||||
|
||||
.input-group.mb-2
|
||||
input.form-control(type='text', [(ngModel)]='newForward.host')
|
||||
.input-group-append
|
||||
.input-group-text :
|
||||
input.form-control(type='number', [(ngModel)]='newForward.port')
|
||||
.input-group-append
|
||||
.input-group-text →
|
||||
input.form-control(type='text', [(ngModel)]='newForward.targetAddress')
|
||||
.input-group-append
|
||||
.input-group-text :
|
||||
input.form-control(type='number', [(ngModel)]='newForward.targetPort')
|
||||
|
||||
.d-flex
|
||||
.btn-group.mr-auto(
|
||||
[(ngModel)]='newForward.type',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary.m-0(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='PortForwardType.Local'
|
||||
)
|
||||
| Local
|
||||
label.btn.btn-secondary.m-0(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
[value]='PortForwardType.Remote'
|
||||
)
|
||||
| Remote
|
||||
|
||||
button.btn.btn-primary((click)='addForward()')
|
||||
i.fas.fa-check.mr-2
|
||||
span Forward port
|
@@ -0,0 +1,42 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ForwardedPort, PortForwardType, SSHSession } from '../api'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./sshPortForwardingModal.component.pug'),
|
||||
// styles: [require('./sshPortForwardingModal.component.scss')],
|
||||
})
|
||||
export class SSHPortForwardingModalComponent {
|
||||
@Input() session: SSHSession
|
||||
newForward = new ForwardedPort()
|
||||
PortForwardType = PortForwardType
|
||||
|
||||
constructor (
|
||||
public modalInstance: NgbActiveModal,
|
||||
) {
|
||||
this.reset()
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.newForward = new ForwardedPort()
|
||||
this.newForward.type = PortForwardType.Local
|
||||
this.newForward.host = '127.0.0.1'
|
||||
this.newForward.port = 8000
|
||||
this.newForward.targetAddress = '127.0.0.1'
|
||||
this.newForward.targetPort = 80
|
||||
}
|
||||
|
||||
async addForward () {
|
||||
try {
|
||||
await this.session.addPortForward(this.newForward)
|
||||
this.reset()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
remove (fw: ForwardedPort) {
|
||||
this.session.removePortForward(fw)
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ export class SSHSettingsTabComponent {
|
||||
createConnection () {
|
||||
const connection: SSHConnection = {
|
||||
name: '',
|
||||
group: null,
|
||||
host: '',
|
||||
port: 22,
|
||||
user: 'root',
|
||||
@@ -97,7 +98,7 @@ export class SSHSettingsTabComponent {
|
||||
}
|
||||
)).response === 1) {
|
||||
for (const connection of this.connections.filter(x => x.group === group.name)) {
|
||||
connection.group = undefined
|
||||
connection.group = null
|
||||
}
|
||||
this.config.save()
|
||||
this.refresh()
|
||||
@@ -109,7 +110,7 @@ export class SSHSettingsTabComponent {
|
||||
this.childGroups = []
|
||||
|
||||
for (const connection of this.connections) {
|
||||
connection.group = connection.group || undefined
|
||||
connection.group = connection.group || null
|
||||
let group = this.childGroups.find(x => x.name === connection.group)
|
||||
if (!group) {
|
||||
group = {
|
||||
|
3
terminus-ssh/src/components/sshTab.component.pug
Normal file
3
terminus-ssh/src/components/sshTab.component.pug
Normal file
@@ -0,0 +1,3 @@
|
||||
button.btn.btn-outline-secondary((click)='showPortForwarding()')
|
||||
i.fas.fa-plug
|
||||
span Ports
|
@@ -3,6 +3,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&> .content {
|
||||
flex: auto;
|
||||
@@ -11,4 +12,11 @@
|
||||
overflow: hidden;
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
&> button {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 40px;
|
||||
z-index: 4;
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,14 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { BaseTerminalTabComponent } from 'terminus-terminal'
|
||||
import { SSHService } from '../services/ssh.service'
|
||||
import { SSHConnection, SSHSession } from '../api'
|
||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: BaseTerminalTabComponent.template,
|
||||
template: BaseTerminalTabComponent.template + require<string>('./sshTab.component.pug'),
|
||||
styles: [require('./sshTab.component.scss'), ...BaseTerminalTabComponent.styles],
|
||||
animations: BaseTerminalTabComponent.animations,
|
||||
})
|
||||
@@ -14,8 +16,11 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
connection: SSHConnection
|
||||
ssh: SSHService
|
||||
session: SSHSession
|
||||
private ngbModal: NgbModal
|
||||
|
||||
ngOnInit () {
|
||||
this.ngbModal = this.injector.get<NgbModal>(NgbModal)
|
||||
|
||||
this.logger = this.log.create('terminalTab')
|
||||
this.ssh = this.injector.get(SSHService)
|
||||
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||
@@ -35,7 +40,11 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
return
|
||||
}
|
||||
|
||||
this.session = new SSHSession(this.connection)
|
||||
this.session = this.ssh.createSession(this.connection)
|
||||
this.session.serviceMessage$.subscribe(msg => {
|
||||
this.write(`\r\n[SSH] ${msg}\r\n`)
|
||||
this.session.resize(this.size.columns, this.size.rows)
|
||||
})
|
||||
this.attachSessionHandlers()
|
||||
this.write(`Connecting to ${this.connection.host}`)
|
||||
const interval = setInterval(() => this.write('.'), 500)
|
||||
@@ -51,8 +60,8 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
clearInterval(interval)
|
||||
this.write('\r\n')
|
||||
}
|
||||
await this.session.start()
|
||||
this.session.resize(this.size.columns, this.size.rows)
|
||||
this.session.start()
|
||||
}
|
||||
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
@@ -61,4 +70,9 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
connection: this.connection,
|
||||
}
|
||||
}
|
||||
|
||||
showPortForwarding () {
|
||||
const modal = this.ngbModal.open(SSHPortForwardingModalComponent).componentInstance as SSHPortForwardingModalComponent
|
||||
modal.session = this.session
|
||||
}
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import TerminusTerminalModule from 'terminus-terminal'
|
||||
|
||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||
import { SSHModalComponent } from './components/sshModal.component'
|
||||
import { SSHPortForwardingModalComponent } from './components/sshPortForwardingModal.component'
|
||||
import { PromptModalComponent } from './components/promptModal.component'
|
||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||
import { SSHTabComponent } from './components/sshTab.component'
|
||||
@@ -40,6 +41,7 @@ import { SSHHotkeyProvider } from './hotkeys'
|
||||
EditConnectionModalComponent,
|
||||
PromptModalComponent,
|
||||
SSHModalComponent,
|
||||
SSHPortForwardingModalComponent,
|
||||
SSHSettingsTabComponent,
|
||||
SSHTabComponent,
|
||||
],
|
||||
@@ -47,6 +49,7 @@ import { SSHHotkeyProvider } from './hotkeys'
|
||||
EditConnectionModalComponent,
|
||||
PromptModalComponent,
|
||||
SSHModalComponent,
|
||||
SSHPortForwardingModalComponent,
|
||||
SSHSettingsTabComponent,
|
||||
SSHTabComponent,
|
||||
],
|
||||
|
@@ -20,7 +20,7 @@ export class SSHService {
|
||||
private logger: Logger
|
||||
|
||||
private constructor (
|
||||
log: LogService,
|
||||
private log: LogService,
|
||||
private app: AppService,
|
||||
private zone: NgZone,
|
||||
private ngbModal: NgbModal,
|
||||
@@ -38,6 +38,12 @@ export class SSHService {
|
||||
) as SSHTabComponent)
|
||||
}
|
||||
|
||||
createSession (connection: SSHConnection): SSHSession {
|
||||
const session = new SSHSession(connection)
|
||||
session.logger = this.log.create(`ssh-${connection.host}-${connection.port}`)
|
||||
return session
|
||||
}
|
||||
|
||||
async connectSession (session: SSHSession, logCallback?: (s: any) => void): Promise<void> {
|
||||
let privateKey: string|null = null
|
||||
let privateKeyPassphrase: string|null = null
|
||||
@@ -91,6 +97,7 @@ export class SSHService {
|
||||
}
|
||||
|
||||
const ssh = new Client()
|
||||
session.ssh = ssh
|
||||
let connected = false
|
||||
let savedPassword: string|null = null
|
||||
await new Promise(async (resolve, reject) => {
|
||||
@@ -210,31 +217,6 @@ export class SSHService {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
try {
|
||||
const shell: any = await new Promise<any>((resolve, reject) => {
|
||||
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(shell)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
session.shell = shell
|
||||
|
||||
shell.on('greeting', greeting => {
|
||||
log(`Shell Greeting: ${greeting}`)
|
||||
})
|
||||
|
||||
shell.on('banner', banner => {
|
||||
log(`Shell Banner: ${banner}`)
|
||||
})
|
||||
} catch (error) {
|
||||
this.toastr.error(error.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -46,27 +46,32 @@ safer-buffer@~2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
ssh2-streams@^0.4.2, ssh2-streams@~0.4.7:
|
||||
version "0.4.7"
|
||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.7.tgz#093b89069de9cf5f06feff0601a5301471b01611"
|
||||
integrity sha512-JhF8BNfeguOqVHOLhXjzLlRKlUP8roAEhiT/y+NcBQCqpRUupLNrRf2M+549OPNVGx21KgKktug4P3MY/IvTig==
|
||||
ssh2-streams@^0.4.2, ssh2-streams@~0.4.8:
|
||||
version "0.4.8"
|
||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.8.tgz#2ff92df2e0063fef86cf934eaea197967deda715"
|
||||
integrity sha512-auxXfgYySz2vYw7TMU7PK7vFI7EPvhvTH8/tZPgGaWocK4p/vwCMiV3icz9AEkb0R40kOKZtFtqYIxDJyJiytw==
|
||||
dependencies:
|
||||
asn1 "~0.2.0"
|
||||
bcrypt-pbkdf "^1.0.2"
|
||||
streamsearch "~0.1.2"
|
||||
|
||||
ssh2@^0.8.2:
|
||||
version "0.8.6"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.6.tgz#dcc62e1d3b9e58a21f711f5186f043e4e792e6da"
|
||||
integrity sha512-T0cPmEtmtC8WxSupicFDjx3vVUdNXO8xu2a/D5bjt8ixOUCe387AgvxU3mJgEHpu7+Sq1ZYx4d3P2pl/yxMH+w==
|
||||
version "0.8.7"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.7.tgz#2dc15206f493010b98027201cf399b90bab79c89"
|
||||
integrity sha512-/u1BO12kb0lDVxJXejWB9pxyF3/ncgRqI9vPCZuPzo05pdNDzqUeQRavScwSPsfMGK+5H/VRqp1IierIx0Bcxw==
|
||||
dependencies:
|
||||
ssh2-streams "~0.4.7"
|
||||
ssh2-streams "~0.4.8"
|
||||
|
||||
streamsearch@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
terminus-terminal@^1.0.98-nightly.0:
|
||||
version "1.0.98-nightly.0"
|
||||
resolved "https://registry.yarnpkg.com/terminus-terminal/-/terminus-terminal-1.0.98-nightly.0.tgz#10df71b0a81adf76a076fb21a91c859dd2f8bef7"
|
||||
integrity sha512-JLxkeoQkORcfe6cRW6BJF5ZPSbvKA8IWUAb7fzBONVmNfRKj2Mq/uYPy76UXsdmb9F1n+rYIg+DShNp57asMKA==
|
||||
|
||||
tweetnacl@^0.14.3:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-terminal",
|
||||
"version": "1.0.93-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Terminus' terminal emulation core",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
@@ -25,13 +25,13 @@
|
||||
"mz": "^2.6.0",
|
||||
"ps-node": "^0.1.6",
|
||||
"runes": "^0.4.2",
|
||||
"slug": "^1.1.0",
|
||||
"slug": "^2.0.0",
|
||||
"uuid": "^3.3.2",
|
||||
"xterm": "3.15.0-beta98",
|
||||
"xterm-addon-fit": "^0.4.0-beta1",
|
||||
"xterm-addon-ligatures": "^0.2.0",
|
||||
"xterm-addon-search": "^0.4.0-beta3",
|
||||
"xterm-addon-webgl": "^0.4.0-beta1"
|
||||
"xterm": "4.3.0",
|
||||
"xterm-addon-fit": "^0.4.0-beta2",
|
||||
"xterm-addon-ligatures": "^0.2.1",
|
||||
"xterm-addon-search": "^0.4.0",
|
||||
"xterm-addon-webgl": "^0.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "^7",
|
||||
|
@@ -22,8 +22,8 @@ export interface ToastrServiceProxy {
|
||||
* A class to base your custom terminal tabs on
|
||||
*/
|
||||
export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
|
||||
static template = require('../components/baseTerminalTab.component.pug')
|
||||
static styles = [require('../components/terminalTab.component.scss')]
|
||||
static template = require<string>('../components/baseTerminalTab.component.pug')
|
||||
static styles = [require<string>('../components/terminalTab.component.scss')]
|
||||
static animations: AnimationTriggerMetadata[] = [trigger('slideInOut', [
|
||||
transition(':enter', [
|
||||
style({ transform: 'translateY(-25%)' }),
|
||||
@@ -63,7 +63,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
private bellPlayer: HTMLAudioElement
|
||||
private termContainerSubscriptions: Subscription[] = []
|
||||
|
||||
get input$ (): Observable<string> { return this.frontend.input$ }
|
||||
get input$ (): Observable<Buffer> { return this.frontend.input$ }
|
||||
get output$ (): Observable<string> { return this.output }
|
||||
get resize$ (): Observable<ResizeEvent> { return this.frontend.resize$ }
|
||||
get alternateScreenActive$ (): Observable<boolean> { return this.frontend.alternateScreenActive$ }
|
||||
@@ -229,7 +229,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
/**
|
||||
* Feeds input into the active session
|
||||
*/
|
||||
sendInput (data: string) {
|
||||
sendInput (data: string|Buffer) {
|
||||
if (!(data instanceof Buffer)) {
|
||||
data = Buffer.from(data, 'utf-8')
|
||||
}
|
||||
this.session.write(data)
|
||||
if (this.config.store.terminal.scrollOnInput) {
|
||||
this.frontend.scrollToBottom()
|
||||
@@ -350,7 +353,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
this.frontend.mouseEvent$.subscribe(async event => {
|
||||
if (event.type === 'mousedown') {
|
||||
if (event.which === 2) {
|
||||
this.paste()
|
||||
if (this.config.store.terminal.pasteOnMiddleClick) {
|
||||
this.paste()
|
||||
}
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
return
|
||||
|
@@ -1,24 +1,11 @@
|
||||
h3.mb-3 Appearance
|
||||
.d-flex
|
||||
.mr-5
|
||||
.form-line
|
||||
.header
|
||||
.title Frontend
|
||||
.description Switches terminal frontend implementation (experimental)
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.frontend',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option(value='hterm') hterm
|
||||
option(value='xterm') xterm
|
||||
option(value='xterm-webgl') xterm (WebGL)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Font
|
||||
|
||||
.d-flex.w-75
|
||||
.input-group.w-75
|
||||
input.form-control.w-75(
|
||||
type='text',
|
||||
[ngbTypeahead]='fontAutocomplete',
|
||||
@@ -52,9 +39,10 @@ h3.mb-3 Appearance
|
||||
)
|
||||
option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}}
|
||||
option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}}
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)') Edit
|
||||
.input-group-btn
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)')
|
||||
i.fas.fa-pen
|
||||
.input-group-append
|
||||
button.btn.btn-outline-danger(
|
||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
||||
@@ -65,10 +53,12 @@ h3.mb-3 Appearance
|
||||
label Editing
|
||||
.input-group
|
||||
input.form-control(type='text', [(ngModel)]='editingColorScheme.name')
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='saveScheme()') Save
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='cancelEditing()') Cancel
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='saveScheme()')
|
||||
i.fas.fa-check
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='cancelEditing()')
|
||||
i.fas.fa-times
|
||||
|
||||
.form-group(*ngIf='editingColorScheme')
|
||||
color-picker(
|
||||
@@ -180,6 +170,19 @@ h3.mb-3 Appearance
|
||||
span rm -rf /
|
||||
span([style.background-color]='config.store.terminal.colorScheme.cursor')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Frontend
|
||||
.description Switches terminal frontend implementation (experimental)
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.frontend',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option(value='hterm') hterm
|
||||
option(value='xterm') xterm
|
||||
option(value='xterm-webgl') xterm (WebGL)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Terminal background
|
||||
|
@@ -2,55 +2,55 @@
|
||||
.form-group
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='profile.name',
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='profile.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Command
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.command',
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.command',
|
||||
)
|
||||
|
||||
|
||||
.form-group
|
||||
label Arguments
|
||||
.input-group(
|
||||
*ngFor='let arg of profile.sessionOptions.args; index as i; trackBy: trackByIndex',
|
||||
)
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.args[i]',
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.args[i]',
|
||||
)
|
||||
.input-group-btn
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.splice(i, 1)')
|
||||
i.fas.fa-trash
|
||||
|
||||
|
||||
.mt-2
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.push("")')
|
||||
i.fas.fa-plus.mr-2
|
||||
| Add
|
||||
|
||||
|
||||
.form-line(*ngIf='uac.isAvailable')
|
||||
.header
|
||||
.title Run as administrator
|
||||
toggle(
|
||||
[(ngModel)]='profile.sessionOptions.runAsAdministrator',
|
||||
)
|
||||
|
||||
|
||||
.form-group
|
||||
label Working directory
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.cwd',
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.cwd',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Environment
|
||||
environment-editor(
|
||||
type='text',
|
||||
[(model)]='profile.sessionOptions.env',
|
||||
type='text',
|
||||
[(model)]='profile.sessionOptions.env',
|
||||
)
|
||||
|
||||
.modal-footer
|
||||
|
@@ -1,12 +1,13 @@
|
||||
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
|
||||
.input-group.w-50
|
||||
input.form-control([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
|
||||
.input-group-append
|
||||
.input-group
|
||||
input.form-control.w-25([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
|
||||
.input-group-append
|
||||
.input-group-text =
|
||||
input.form-control.w-50.mr-1([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
|
||||
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
||||
i.fas.fa-trash
|
||||
|
||||
input.form-control.w-50([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
||||
i.fas.fa-trash
|
||||
|
||||
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
||||
i.fas.fa-plus.mr-2
|
||||
span Add
|
||||
|
@@ -2,7 +2,7 @@
|
||||
input.search-input.form-control(
|
||||
type='search',
|
||||
[(ngModel)]='query',
|
||||
(ngModelChange)='notFound = false',
|
||||
(ngModelChange)='onQueryChange()',
|
||||
[class.text-danger]='notFound',
|
||||
(click)='$event.stopPropagation()',
|
||||
(keyup.enter)='findNext()',
|
||||
@@ -10,31 +10,27 @@
|
||||
placeholder='Search...'
|
||||
)
|
||||
.input-group-append
|
||||
.btn-group
|
||||
button.btn.btn-outline-primary(
|
||||
.input-group-text
|
||||
a(
|
||||
(click)='options.caseSensitive = !options.caseSensitive',
|
||||
[class.active]='options.caseSensitive',
|
||||
[class.text-info]='options.caseSensitive',
|
||||
ngbTooltip='Case sensitivity',
|
||||
placement='bottom'
|
||||
)
|
||||
i.fa.fa-fw.fa-font
|
||||
button.btn.btn-outline-primary(
|
||||
a(
|
||||
(click)='options.regex = !options.regex',
|
||||
[class.active]='options.regex',
|
||||
[class.text-info]='options.regex',
|
||||
ngbTooltip='Regular expression',
|
||||
placement='bottom'
|
||||
)
|
||||
i.fa.fa-fw.fa-asterisk
|
||||
button.btn.btn-outline-primary(
|
||||
a(
|
||||
(click)='options.wholeWord = !options.wholeWord',
|
||||
[class.active]='options.wholeWord',
|
||||
[class.text-info]='options.wholeWord',
|
||||
ngbTooltip='Whole word',
|
||||
placement='bottom'
|
||||
)
|
||||
i.fa.fa-fw.fa-text-width
|
||||
button.btn.btn-outline(
|
||||
(click)='close.emit()',
|
||||
ngbTooltip='Close',
|
||||
placement='bottom'
|
||||
)
|
||||
i.fa.fa-fw.fa-times
|
||||
|
||||
button.close.text-light.pl-3.pr-2((click)='close.emit()') ×
|
||||
|
@@ -1,9 +1,20 @@
|
||||
:host {
|
||||
position: fixed;
|
||||
width: 400px;
|
||||
align-self: center;
|
||||
right: 50px;
|
||||
z-index: 5;
|
||||
padding: 10px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: rgba(0, 0, 0, .75);
|
||||
border: 1px solid rgba(0, 0, 0, .5);
|
||||
border-top: 0;
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
padding-left: 10px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,9 @@ export class SearchPanelComponent {
|
||||
@Input() query: string
|
||||
@Input() frontend: Frontend
|
||||
notFound = false
|
||||
options: SearchOptions = {}
|
||||
options: SearchOptions = {
|
||||
incremental: true,
|
||||
}
|
||||
|
||||
@Output() close = new EventEmitter()
|
||||
|
||||
@@ -19,8 +21,13 @@ export class SearchPanelComponent {
|
||||
private toastr: ToastrService,
|
||||
) { }
|
||||
|
||||
findNext () {
|
||||
if (!this.frontend.findNext(this.query, this.options)) {
|
||||
onQueryChange () {
|
||||
this.notFound = false
|
||||
this.findNext(true)
|
||||
}
|
||||
|
||||
findNext (incremental = false) {
|
||||
if (!this.frontend.findNext(this.query, { ...this.options, incremental: incremental || undefined })) {
|
||||
this.notFound = true
|
||||
this.toastr.error('Not found')
|
||||
}
|
||||
|
@@ -14,20 +14,20 @@ h3.mb-3 Shell
|
||||
[ngValue]='slug(profile.name).toLowerCase()'
|
||||
) {{profile.name}}
|
||||
|
||||
|
||||
|
||||
.form-line(*ngIf='isConPTYAvailable')
|
||||
.header
|
||||
.title Use ConPTY
|
||||
.description Enables the experimental Windows ConPTY API
|
||||
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.useConPTY',
|
||||
(ngModelChange)='config.save()'
|
||||
)
|
||||
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.useConPTY && isConPTYAvailable && !isConPTYStable')
|
||||
.mr-auto Windows 10 build 18309 or above is recommended for ConPTY
|
||||
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.profile.startsWith("WSL") && (config.store.terminal.frontend != "hterm" || !config.store.terminal.useConPTY)')
|
||||
.mr-auto WSL terminal only supports TrueColor with ConPTY and the hterm frontend
|
||||
|
||||
@@ -50,15 +50,17 @@ h3.mb-3 Shell
|
||||
placeholder='Home directory',
|
||||
[(ngModel)]='config.store.terminal.workingDirectory',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
.input-group-btn
|
||||
)
|
||||
.input-group-append
|
||||
button.btn.btn-secondary((click)='pickWorkingDirectory()')
|
||||
i.fas.fa-folder-open
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Always Use Working Directory
|
||||
.description By default, new terminals will open where the previous terminal was working. Enabling this option will always launch new terminals in the working directory specified above.
|
||||
.description
|
||||
div By default, new terminals will open where the previous terminal was working.
|
||||
div Enabling this option will always launch new terminals in the working directory specified above.
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.alwaysUseWorkingDirectory',
|
||||
@@ -69,7 +71,7 @@ h3.mb-3 Shell
|
||||
.header
|
||||
.title Environment
|
||||
.description Inject additional environment variables
|
||||
|
||||
|
||||
environment-editor([(model)]='this.config.store.terminal.environment')
|
||||
|
||||
h3.mt-3 Saved Profiles
|
||||
@@ -78,16 +80,16 @@ h3.mt-3 Saved Profiles
|
||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
*ngFor='let profile of config.store.terminal.profiles',
|
||||
(click)='editProfile(profile)',
|
||||
)
|
||||
)
|
||||
.mr-auto
|
||||
div {{profile.name}}
|
||||
.text-muted {{profile.sessionOptions.command}}
|
||||
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteProfile(profile)')
|
||||
i.fas.fa-trash
|
||||
|
||||
|
||||
div(ngbDropdown, placement='top-left')
|
||||
button.btn.btn-primary(ngbDropdownToggle)
|
||||
button.btn.btn-primary(ngbDropdownToggle)
|
||||
i.fas.fa-fw.fa-plus
|
||||
| New profile
|
||||
| New profile
|
||||
div(ngbDropdownMenu)
|
||||
button.dropdown-item(*ngFor='let shell of shells', (click)='newProfile(shell)') {{shell.name}}
|
||||
|
@@ -42,6 +42,13 @@ h3.mb-3 Terminal
|
||||
(ngModelChange)='config.save()',
|
||||
ngbRadioGroup
|
||||
)
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
ngbButton,
|
||||
value='off'
|
||||
)
|
||||
| Off
|
||||
label.btn.btn-secondary(ngbButtonLabel)
|
||||
input(
|
||||
type='radio',
|
||||
@@ -57,6 +64,15 @@ h3.mb-3 Terminal
|
||||
)
|
||||
| Paste
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Paste on middle-click
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.pasteOnMiddleClick',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Auto-open a terminal on app start
|
||||
|
@@ -25,6 +25,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
cursorBlink: true,
|
||||
customShell: '',
|
||||
rightClick: 'menu',
|
||||
pasteOnMiddleClick: true,
|
||||
copyOnSelect: false,
|
||||
scrollOnInput: true,
|
||||
workingDirectory: '',
|
||||
@@ -113,6 +114,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
shell: 'clink',
|
||||
profile: 'cmd-clink',
|
||||
rightClick: 'paste',
|
||||
pasteOnMiddleClick: false,
|
||||
copyOnSelect: true,
|
||||
},
|
||||
hotkeys: {
|
||||
|
@@ -6,6 +6,7 @@ export interface SearchOptions {
|
||||
regex?: boolean
|
||||
wholeWord?: boolean
|
||||
caseSensitive?: boolean
|
||||
incremental?: true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +24,7 @@ export abstract class Frontend {
|
||||
protected mouseEvent = new Subject<MouseEvent>()
|
||||
protected bell = new Subject<void>()
|
||||
protected contentUpdated = new Subject<void>()
|
||||
protected input = new Subject<string>()
|
||||
protected input = new Subject<Buffer>()
|
||||
protected resize = new ReplaySubject<ResizeEvent>(1)
|
||||
protected dragOver = new Subject<DragEvent>()
|
||||
protected drop = new Subject<DragEvent>()
|
||||
@@ -34,7 +35,7 @@ export abstract class Frontend {
|
||||
get mouseEvent$ (): Observable<MouseEvent> { return this.mouseEvent }
|
||||
get bell$ (): Observable<void> { return this.bell }
|
||||
get contentUpdated$ (): Observable<void> { return this.contentUpdated }
|
||||
get input$ (): Observable<string> { return this.input }
|
||||
get input$ (): Observable<Buffer> { return this.input }
|
||||
get resize$ (): Observable<ResizeEvent> { return this.resize }
|
||||
get dragOver$ (): Observable<DragEvent> { return this.dragOver }
|
||||
get drop$ (): Observable<DragEvent> { return this.drop }
|
||||
|
@@ -182,7 +182,7 @@ export class HTermFrontend extends Frontend {
|
||||
this.term.installKeyboard()
|
||||
this.term.scrollPort_.setCtrlVPaste(true)
|
||||
this.io = this.term.io.push()
|
||||
this.io.onVTKeystroke = this.io.sendString = data => this.input.next(data)
|
||||
this.io.onVTKeystroke = this.io.sendString = data => this.input.next(Buffer.from(data, 'utf-8'))
|
||||
this.io.onTerminalResize = (columns, rows) => {
|
||||
this.resize.next({ columns, rows })
|
||||
}
|
||||
|
@@ -39,8 +39,11 @@ export class XTermFrontend extends Frontend {
|
||||
})
|
||||
this.xtermCore = (this.xterm as any)._core
|
||||
|
||||
this.xterm.onBinary(data => {
|
||||
this.input.next(Buffer.from(data, 'binary'))
|
||||
})
|
||||
this.xterm.onData(data => {
|
||||
this.input.next(data)
|
||||
this.input.next(Buffer.from(data, 'utf-8'))
|
||||
})
|
||||
this.xterm.onResize(({ cols, rows }) => {
|
||||
this.resize.next({ rows, columns: cols })
|
||||
@@ -113,6 +116,8 @@ export class XTermFrontend extends Frontend {
|
||||
}
|
||||
|
||||
attach (host: HTMLElement): void {
|
||||
this.configure()
|
||||
|
||||
this.xterm.open(host)
|
||||
this.opened = true
|
||||
|
||||
@@ -209,7 +214,7 @@ export class XTermFrontend extends Frontend {
|
||||
|
||||
const theme: ITheme = {
|
||||
foreground: config.terminal.colorScheme.foreground,
|
||||
background: config.terminal.background === 'colorScheme' ? config.terminal.colorScheme.background : config.appearance.vibrancy ? 'transparent' : this.themesService.findCurrentTheme().terminalBackground,
|
||||
background: config.terminal.background === 'colorScheme' ? config.terminal.colorScheme.background : config.appearance.vibrancy ? '#00000000' : this.themesService.findCurrentTheme().terminalBackground,
|
||||
cursor: config.terminal.colorScheme.cursor,
|
||||
}
|
||||
|
||||
|
@@ -77,7 +77,7 @@ export abstract class BaseSession {
|
||||
|
||||
abstract start (options: SessionOptions): void
|
||||
abstract resize (columns: number, rows: number): void
|
||||
abstract write (data: string): void
|
||||
abstract write (data: Buffer): void
|
||||
abstract kill (signal?: string): void
|
||||
abstract async getChildProcesses (): Promise<ChildProcess[]>
|
||||
abstract async gracefullyKillProcess (): Promise<void>
|
||||
@@ -201,10 +201,10 @@ export class Session extends BaseSession {
|
||||
}
|
||||
}
|
||||
|
||||
write (data) {
|
||||
write (data: Buffer) {
|
||||
if (this.open) {
|
||||
if (this.pty._writable) {
|
||||
this.pty.write(Buffer.from(data, 'utf-8'))
|
||||
this.pty.write(data)
|
||||
} else {
|
||||
this.destroy()
|
||||
}
|
||||
@@ -292,7 +292,7 @@ export class Session extends BaseSession {
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
try {
|
||||
return await fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||
return fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||
} catch (exc) {
|
||||
console.error(exc)
|
||||
return null
|
||||
|
@@ -2,6 +2,8 @@ import { Injectable, NgZone } from '@angular/core'
|
||||
import { ToastrService } from 'ngx-toastr'
|
||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider } from 'terminus-core'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { UACService } from './services/uac.service'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@@ -10,6 +12,8 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
private config: ConfigService,
|
||||
private zone: NgZone,
|
||||
private toastr: ToastrService,
|
||||
private uac: UACService,
|
||||
private terminalService: TerminalService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -18,7 +22,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
if (!(tab instanceof TerminalTabComponent)) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
const items: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'Save as profile',
|
||||
click: () => this.zone.run(async () => {
|
||||
@@ -38,5 +42,19 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
if (this.uac.isAvailable) {
|
||||
items.push({
|
||||
label: 'Duplicate as administrator',
|
||||
click: () => this.zone.run(async () => {
|
||||
this.terminalService.openTabWithOptions({
|
||||
...tab.sessionOptions,
|
||||
runAsAdministrator: true,
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
@@ -170,10 +170,10 @@ runes@^0.4.2:
|
||||
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
||||
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
|
||||
|
||||
slug@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/slug/-/slug-1.1.0.tgz#73eef5710416f515077bdf70c683bde4915913c9"
|
||||
integrity sha512-NuIOjDQeTMPm+/AUIHJ5636mF3jOsYLFnoEErl9Tdpt4kpt4fOrAJxscH9mUgX1LtPaEqgPCawBg7A4yhoSWRg==
|
||||
slug@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slug/-/slug-2.0.0.tgz#842b60f9f827aac045fe5894d8426e9624578f6f"
|
||||
integrity sha512-nsPIR74De90eXwy6vgO23P92YtpddZZ/AjCHWx9+65TWsq/174hOSzn2Q1RL9bG2aYuxwyNZbHp/IbUhLUwUtw==
|
||||
dependencies:
|
||||
unicode ">= 0.3.1"
|
||||
|
||||
@@ -213,33 +213,33 @@ uuid@^3.3.2:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
|
||||
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
|
||||
|
||||
xterm-addon-fit@^0.4.0-beta1:
|
||||
xterm-addon-fit@^0.4.0-beta2:
|
||||
version "0.4.0-beta2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0-beta2.tgz#c638ea7d41c55b535825f41b1cdb7358a94dfca4"
|
||||
integrity sha512-7EHWk8SPCmKuw9ux1mFek2SfBw1QjJ/ObYA87tubOtJi7mAZ0eIb9IE5ditcma9Nyz/cR/ROQQxoRn4UbDvyfA==
|
||||
|
||||
xterm-addon-ligatures@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-ligatures/-/xterm-addon-ligatures-0.2.0.tgz#8d65fea968ba5b4306b2ada6f53eed3e1984f69c"
|
||||
integrity sha512-IcRgjq3QCcL6P8W5M86BCnXIGD1LcCVKk7w3S29Yn2+YksMM3c85JjW2OUgsL3VTW/Tb6uyxpSV2rJOsRElVrQ==
|
||||
xterm-addon-ligatures@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-ligatures/-/xterm-addon-ligatures-0.2.1.tgz#487125dbb25818a6f88c3464c9a8a0eb4218bdae"
|
||||
integrity sha512-UGoWTM7MBRRXMyGX6oMdaBhrO6SIJTriPo2U+QyQSs4H5J64ZiMZBsJe7ieOLmsKSAC/T+c39moU6sJGbWnylg==
|
||||
dependencies:
|
||||
font-finder "^1.0.4"
|
||||
font-ligatures "^1.3.2"
|
||||
|
||||
xterm-addon-search@^0.4.0-beta3:
|
||||
version "0.4.0-beta5"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta5.tgz#d7b7d35502cc5155d35175ab63e0465c1447eebd"
|
||||
integrity sha512-aA+WmoM6U8H9V4ofKLaZaTBFoRLdxVujWA2mQxbzXBF8FLLCDSJOK8kEor4BSb8OtGr0Nlfs1Qy9O0HBmXSQWA==
|
||||
xterm-addon-search@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1"
|
||||
integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg==
|
||||
|
||||
xterm-addon-webgl@^0.4.0-beta1:
|
||||
version "0.4.0-beta9"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta9.tgz#3e004d5cd893ae678e2537b195ae0eed4d689df3"
|
||||
integrity sha512-+lUsUAx4ATyetRuTuEorUpKD5NpDUUc5Z3chtYV8ECiTJYiDr0CfAxW9oa3tT8BVO7fOTdgxgJpQmsU4LGEm5A==
|
||||
xterm-addon-webgl@^0.4.0:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.1.tgz#c245f3310e7dbb0759e5eabaf125bbc9c9ab3955"
|
||||
integrity sha512-z/YRmAji7y3ngjvSgoNCQxqYioarNplNqWv/KgtF4nRkBTWkhLHAg1QFNcH7XxUV/O8VuZoGAfp4LPX2hVz/yQ==
|
||||
|
||||
xterm@3.15.0-beta98:
|
||||
version "3.15.0-beta98"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta98.tgz#37f37c35577422880e7ef673cc37f9d2a45dd40c"
|
||||
integrity sha512-vZbg2LcRvoiJOgr1MyeLFM9mF4uib3BWUWDHyFc+vZ58CTuK0iczOvFXgk/ySo23ZLqwmHQSigLgmWvZ8J5G0Q==
|
||||
xterm@4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0.tgz#9a302efefe75172d4f7ea3afc20f9bd983f05027"
|
||||
integrity sha512-6dnrC4nxgnRKQzIWwC5HA0mnT9/rpDPZflUIr24gdcdSMTKM1QQcor4qQ/xz4Zerz6AIL/CuuBPypFfzsB63dQ==
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
|
Reference in New Issue
Block a user