Compare commits

...

31 Commits

Author SHA1 Message Date
Eugene Pankov
e24d3d56eb Revert "Delete icon.ico"
This reverts commit aaac14dbd5.
2017-12-14 17:32:41 +01:00
Eugene Pankov
6f35e60468 added missing hotkeys 2017-12-14 15:03:51 +01:00
Eugene Pankov
110b57bc64 added Ctrl-Tab hotkeys for tab switching 2017-12-14 14:56:59 +01:00
Eugene Pankov
fd47a32bdb use yarn for Travis builds 2017-12-14 14:55:31 +01:00
Eugene Pankov
dfd1ffbffc ssh agent support (closes #251) 2017-12-07 20:47:25 +01:00
Eugene Pankov
f841cfeb5e Merge branch 'master' of github.com:Eugeny/terminus 2017-12-07 20:21:58 +01:00
Eugene Pankov
9d2398bc12 fixed tmux argument list handling (closes #231) 2017-12-07 20:21:02 +01:00
Eugene Pankov
da9cee0792 Delete Info.plist 2017-12-05 12:26:39 +01:00
Eugene Pankov
aaac14dbd5 Delete icon.ico 2017-12-05 12:25:40 +01:00
Eugene Pankov
23396b5c53 Delete build.wxs 2017-12-05 12:25:30 +01:00
Eugene Pankov
021390952a Delete elements.wxs 2017-12-05 12:25:16 +01:00
Eugene Pankov
ad59baa4f5 Delete wix-theme.xml 2017-12-05 12:25:06 +01:00
Eugene Pankov
0420b2dbb9 Merge pull request #254 from tnguyen14/update-hacking-npm-install
update instruction to use npx
2017-12-01 15:42:02 +01:00
Tri Nguyen
fab9429707 update instruction to use npx 2017-12-01 09:31:00 -05:00
Eugene Pankov
5b62d5f92a Merge pull request #252 from tnguyen14/local-npx
Include all build tools in project's dependencies
2017-12-01 09:00:30 +01:00
Eugene Pankov
298637a150 Merge branch 'master' of github.com:Eugeny/terminus 2017-11-28 22:02:43 +01:00
Eugene Pankov
49c738451e fixed the private key selection dialog 2017-11-28 22:02:28 +01:00
Eugene Pankov
892b18df4d fixed #250 2017-11-28 20:30:06 +01:00
Tri Nguyen
de6e545f8f Include all build tools in project's dependencies 2017-11-28 00:19:38 -05:00
Eugene Pankov
d9e337aa46 build fix 2017-11-27 22:37:32 +01:00
Eugene Pankov
2881481fc2 higher log verbosity 2017-11-27 22:37:15 +01:00
Eugene Pankov
fa4c59e3c0 support Xresources colors beyond 16 2017-11-27 21:29:35 +01:00
Eugene Pankov
f783e1ab06 ssh plugin fixes 2017-11-27 17:29:45 +01:00
Eugene Pankov
5cdb7527c8 added SSH connection manager (fixes #220) 2017-11-27 16:30:59 +01:00
Eugene Pankov
13a76db9af Merge branch 'master' of github.com:Eugeny/terminus 2017-11-26 22:14:57 +01:00
Eugene Pankov
0de12b6b38 allow disabling plugins 2017-11-26 22:14:46 +01:00
Eugene Pankov
92993db122 fixed #234 2017-11-21 22:16:59 +01:00
Eugene Pankov
02082c385c include wincredmgr for Windows 2017-11-21 22:15:23 +01:00
Eugene Pankov
0c15f5033d build fix 2017-11-18 23:07:02 +01:00
Eugene Pankov
a280658bbb added xkeyring dep 2017-11-18 22:41:13 +01:00
Eugene Pankov
3673542197 bumped plugins 2017-11-18 22:40:57 +01:00
61 changed files with 5108 additions and 390 deletions

View File

@@ -14,12 +14,12 @@ cache:
- app/node_modules
before_install:
- npm i
- yarn install
- scripts/install-deps.js
script:
- scripts/build-native.js
- npm run build
- yarn run build
- scripts/prepackage-plugins.js
- scripts/build-$BUILD_FOR.js
@@ -32,3 +32,7 @@ addons:
- rpm
- wine
- mono-runtime
- yarn
sources:
- sourceline: 'deb https://dl.yarnpkg.com/debian/ stable main'
key_url: 'https://dl.yarnpkg.com/debian/pubkey.gpg'

View File

@@ -10,14 +10,13 @@ First, install the dependencies:
```
# macOS/Linux:
sudo npm -g install yarn node-gyp
yarn install
npm install
./scripts/install-deps.js
./scripts/build-native.js
# Windows:
npm -g install yarn node-gyp windows-build-tools
yarn install
npm -g install windows-build-tools
npm install
node scripts\install-deps.js
node scripts\build-native.js
```

View File

@@ -5,7 +5,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
export function getRootModule (plugins: any[]) {
let imports = [
BrowserModule,
...(plugins.map(x => x.default.forRoot ? x.default.forRoot() : x.default)),
...plugins,
NgbModule.forRoot(),
]
let bootstrap = [

View File

@@ -30,6 +30,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgM
(document.querySelector('.progress .bar') as HTMLElement).style.width = 100 * current / total + '%'
})
let module = getRootModule(pluginsModules)
window['rootModule'] = module
return await platformBrowserDynamic().bootstrapModule(module)
}

View File

@@ -82,7 +82,7 @@ nodeRequire('module').prototype.require = function (query) {
export async function findPlugins (): Promise<IPluginInfo[]> {
let paths = nodeModule.globalPaths
let foundPlugins: IPluginInfo[] = []
let candidateLocations: { pluginDir: string, pluginName: string }[] = []
let candidateLocations: { pluginDir: string, packageName: string }[] = []
for (let pluginDir of paths) {
pluginDir = normalizePath(pluginDir)
@@ -93,24 +93,26 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
if (await fs.exists(path.join(pluginDir, 'package.json'))) {
candidateLocations.push({
pluginDir: path.dirname(pluginDir),
pluginName: path.basename(pluginDir)
packageName: path.basename(pluginDir)
})
}
for (let pluginName of pluginNames) {
candidateLocations.push({ pluginDir, pluginName })
for (let packageName of pluginNames) {
candidateLocations.push({ pluginDir, packageName })
}
}
for (let { pluginDir, pluginName } of candidateLocations) {
let pluginPath = path.join(pluginDir, pluginName)
for (let { pluginDir, packageName } of candidateLocations) {
let pluginPath = path.join(pluginDir, packageName)
let infoPath = path.join(pluginPath, 'package.json')
if (!await fs.exists(infoPath)) {
continue
}
if (foundPlugins.some(x => x.name === pluginName.substring('terminus-'.length))) {
console.info(`Plugin ${pluginName} already exists, overriding`)
foundPlugins = foundPlugins.filter(x => x.name !== pluginName.substring('terminus-'.length))
let name = packageName.substring('terminus-'.length)
if (foundPlugins.some(x => x.name === name)) {
console.info(`Plugin ${packageName} already exists, overriding`)
foundPlugins = foundPlugins.filter(x => x.name !== name)
}
try {
@@ -121,8 +123,8 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
let author = info.author
author = author.name || author
foundPlugins.push({
name: pluginName.substring('terminus-'.length),
packageName: pluginName,
name: name,
packageName: packageName,
isBuiltin: pluginDir === builtinPluginsPath,
version: info.version,
description: info.description,
@@ -131,7 +133,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
info,
})
} catch (error) {
console.error('Cannot load package info for', pluginName)
console.error('Cannot load package info for', packageName)
}
}
@@ -147,7 +149,10 @@ export async function loadPlugins (foundPlugins: IPluginInfo[], progress: Progre
console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
progress(index, foundPlugins.length)
try {
let pluginModule = nodeRequire(foundPlugin.path)
let packageModule = nodeRequire(foundPlugin.path)
let pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
pluginModule['pluginName'] = foundPlugin.name
pluginModule['bootstrap'] = packageModule.bootstrap
plugins.push(pluginModule)
} catch (error) {
console.error(`Could not load ${foundPlugin.name}:`, error)

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en_US</string>
<key>CFBundleExecutable</key>
<string>ELEMENTS</string>
<key>CFBundleIdentifier</key>
<string>com.syslink.elements</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${VERSION}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Elements Client</string>
<key>CFBundleURLSchemes</key>
<array>
<string>elements-client</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>619</string>
<key>LSMinimumSystemVersion</key>
<string>10.8.0</string>
<key>LSUIElement</key>
<false/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Syslink GmbH. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>AtomApplication</string>
</dict>
</plist>

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix RequiredVersion="3.6.2830.0" xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="ELEMENTS" Version="$(var.Version)" Manufacturer="ELEMENTS.tv" UpgradeCode="508475fc-0e76-4cd1-8e98-6953023ba518"
HelpUrl="http://elements.tv"
Copyright="Copyright © 2016 ELEMENTS" IconSourceFile="build/icon.ico"
AboutUrl="http://elements.tv">
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
<bal:WixStandardBootstrapperApplication
LicenseUrl=""
LogoFile="build\logo.png"
ThemeFile="build\windows\wix-theme.xml"
/>
</BootstrapperApplicationRef>
<Chain>
<MsiPackage
Id="ClientMSI"
Compressed="yes"
ForcePerMachine="yes"
SourceFile="dist\elements-app.msi"
Vital="yes">
</MsiPackage>
<MsiPackage
Id="DriverMSI"
Compressed="yes"
ForcePerMachine="yes"
SourceFile="build/windows/ElementsDriver_x64.msi"
Vital="yes">
</MsiPackage>
</Chain>
</Bundle>
</Wix>

View File

@@ -1,84 +0,0 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="37484543-5276-2386-5427-275941245342"
Name="ELEMENTS" Version="$(var.ProductVersion)" Manufacturer="ELEMENTS.tv" Language="1033">
<Package InstallerVersion="200"
Compressed="yes"
Comments="Windows Installer Package"
Platform="x64"
InstallScope="perMachine"
InstallPrivileges="elevated" />
<MajorUpgrade AllowDowngrades="yes" Schedule="afterInstallValidate" />
<Media Id="1" Cabinet="product.cab" EmbedCab="yes"/>
<Feature Id="DefaultFeature" Level="1">
<ComponentRef Id="RegistryEntries" />
<ComponentRef Id="AppDir"/>
<ComponentRef Id="AvidSharedStorageAccess"/>
<ComponentRef Id="ApplicationShortcutDesktop"/>
<ComponentGroupRef Id="Files" />
</Feature>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" Name="Desktop">
<Component Id="ApplicationShortcutDesktop" Guid="*">
<Shortcut Id="ApplicationDesktopShortcut"
Name="ELEMENTS"
Description="ELEMENTS client app"
Target="[INSTALLDIR]\\Elements.exe"
WorkingDirectory="INSTALLDIR"/>
<RemoveFolder Id="DesktopFolder" On="uninstall"/>
<RegistryValue
Root="HKCU"
Key="Software\ELEMENTS"
Name="installed"
Type="integer"
Value="1"
KeyPath="yes"/>
</Component>
</Directory>
<Directory Id="ProgramFiles64Folder">
<Directory Id="ElementsDir" Name="ELEMENTS">
<Directory Id="INSTALLDIR" Name="ELEMENTS Client">
<Component Id="AppDir" Guid="284957a6-a462-4e34-babd-c17800f11054" Win64="yes">
<CreateFolder />
<!--RemoveFile Id="RemoveFilesFromAppDirectory" Name="*.*" On="uninstall" /-->
<!--RemoveFolder Id="AppDir" On="uninstall"/-->
</Component>
</Directory>
</Directory>
</Directory>
<Component Id="RegistryEntries" Guid="572998d8-719e-4124-8fe6-6d4f8b855d7b">
<RegistryKey Root="HKLM"
Key="system\currentcontrolset\services\AvidFs"
Action="create">
<RegistryValue Type="string" Name="Description" Value="AIFMRX" />
<RegistryValue Type="string" Name="DisplayName" Value="AIFMRX" />
<RegistryValue Type="integer" Name="ErrorControl" Value="1" />
<RegistryValue Type="string" Name="Group" Value="Network" />
<RegistryValue Type="string" Name="ImagePath" Value="System32\DRIVERS\aifmrx.sys" />
<RegistryValue Type="integer" Name="Start" Value="1" />
<RegistryValue Type="integer" Name="Type" Value="2" />
</RegistryKey>
<RegistryKey Root="HKLM"
Key="system\currentcontrolset\services\AifMRx\NetworkProvider"
Action="create">
<RegistryValue Type="string" Name="DeviceName" Value="\Device\AvidFs" />
<RegistryValue Type="string" Name="Name" Value="Interface Network" />
<RegistryValue Type="string" Name="ProviderPath" Value="System32\aifmrxnp.dll" />
</RegistryKey>
<RegistryKey Root="HKLM"
Key="system\CurrentControlSet\services\LanmanWorkstation\Parameters"
Action="create">
<RegistryValue Type="integer" Name="DisableLargeMtu" Value="0" KeyPath="yes" />
<RegistryValue Type="integer" Name="DisableBandwidthThrottling" Value="1" />
<RegistryValue Type="integer" Name="EnableWsd" Value="0" />
</RegistryKey>
</Component>
<Directory Id="System64Folder" Name="SystemFolder">
<Component Id="AvidSharedStorageAccess" Guid="972c67f2-ee17-4b20-8939-b92cfa13fcf6" NeverOverwrite="yes" Win64="yes" Permanent="yes">
<File Id="AvidSharedStorageAccess.dll" Source="build\windows\AvidSharedStorageAccess.dll" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
</Product>
</Wix>

View File

@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<Theme xmlns="http://wixtoolset.org/schemas/thmutil/2010">
<Window Width="300" Height="360" HexStyle="100a0000" FontId="0">#(loc.Caption)</Window>
<Font Id="0" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
<Font Id="1" Height="-24" Weight="500" Foreground="000000">Segoe UI</Font>
<Font Id="2" Height="-22" Weight="500" Foreground="666666">Segoe UI</Font>
<Font Id="3" Height="-12" Weight="500" Foreground="000000" Background="FFFFFF">Segoe UI</Font>
<Font Id="4" Height="-12" Weight="500" Foreground="ff0000" Background="FFFFFF" Underline="yes">Segoe UI</Font>
<Image X="30" Y="30" Width="256" Height="60" ImageFile="logo.png" Visible="yes"/>
<Page Name="Help">
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.HelpHeader)</Text>
<Text X="11" Y="112" Width="-11" Height="-35" FontId="3" DisablePrefix="yes">#(loc.HelpText)</Text>
<Button Name="HelpCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.HelpCloseButton)</Button>
</Page>
<Page Name="Install">
<!--Hypertext Name="EulaHyperlink" X="11" Y="121" Width="-11" Height="51" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallLicenseLinkText)</Hypertext>
<Checkbox Name="EulaAcceptCheckbox" X="-11" Y="-41" Width="260" Height="17" TabStop="yes" FontId="3" HideWhenDisabled="yes">#(loc.InstallAcceptCheckbox)</Checkbox-->
<Button Name="InstallButton" X="90" Y="-120" Width="120" Height="50" TabStop="yes" FontId="0">#(loc.InstallInstallButton)</Button>
<Button Name="WelcomeCancelButton" X="110" Y="-80" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.InstallCloseButton)</Button>
</Page>
<Page Name="Options">
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.OptionsHeader)</Text>
<Text X="11" Y="121" Width="-11" Height="17" FontId="3" DisablePrefix="yes">#(loc.OptionsLocationLabel)</Text>
<Editbox Name="FolderEditbox" X="11" Y="143" Width="-91" Height="21" TabStop="yes" FontId="3" FileSystemAutoComplete="yes" />
<Button Name="BrowseButton" X="-11" Y="142" Width="75" Height="23" TabStop="yes" FontId="3">#(loc.OptionsBrowseButton)</Button>
<Button Name="OptionsOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsOkButton)</Button>
<Button Name="OptionsCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.OptionsCancelButton)</Button>
</Page>
<Page Name="FilesInUse">
<Text X="11" Y="80" Width="-11" Height="30" FontId="2" DisablePrefix="yes">#(loc.FilesInUseHeader)</Text>
<Text X="11" Y="121" Width="-11" Height="34" FontId="3" DisablePrefix="yes">#(loc.FilesInUseLabel)</Text>
<Text Name="FilesInUseText" X="11" Y="150" Width="-11" Height="-86" FontId="3" DisablePrefix="yes" HexStyle="0x0000C000"></Text>
<Button Name="FilesInUseCloseRadioButton" X="11" Y="-60" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseCloseRadioButton)</Button>
<Button Name="FilesInUseDontCloseRadioButton" X="11" Y="-40" Width="-11" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes" HexStyle="0x000009">#(loc.FilesInUseDontCloseRadioButton)</Button>
<Button Name="FilesInUseOkButton" X="-91" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FilesInUseOkButton)</Button>
<Button Name="FilesInUseCancelButton" X="-11" Y="-11" Width="75" Height="23" TabStop="yes" FontId="0">#(loc.FilesInUseCancelButton)</Button>
</Page>
<Page Name="Progress">
<Text X="30" Y="120" Width="-30" Height="30" FontId="2" DisablePrefix="yes">#(loc.ProgressHeader)</Text>
<Text X="30" Y="150" Width="70" Height="17" FontId="3" DisablePrefix="yes">#(loc.ProgressLabel)</Text>
<Text Name="OverallProgressPackageText" X="30" Y="200" Width="-30" Height="17" FontId="3" DisablePrefix="yes">#(loc.OverallProgressPackageText)</Text>
<Progressbar Name="OverallCalculatedProgressbar" X="30" Y="220" Width="-30" Height="20" />
<Button Name="ProgressCancelButton" X="110" Y="-40" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ProgressCancelButton)</Button>
</Page>
<Page Name="Modify">
<Text X="30" Y="110" Width="-30" Height="30" FontId="2" DisablePrefix="yes">#(loc.ModifyHeader)</Text>
<Button Name="UninstallButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0">#(loc.ModifyUninstallButton)</Button>
<Button Name="RepairButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ModifyRepairButton)</Button>
<Button Name="ModifyCancelButton" X="110" Y="-30" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.ModifyCloseButton)</Button>
</Page>
<Page Name="Success">
<Text Name="SuccessHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Success</Text>
<Text Name="SuccessInstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Installed successfully</Text>
<Text Name="SuccessRepairHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Repaired successfully</Text>
<Text Name="SuccessUninstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Uninstalled successfully</Text>
<Button Name="LaunchButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.SuccessLaunchButton)</Button>
<Button Name="SuccessCancelButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.SuccessCloseButton)</Button>
</Page>
<Page Name="Failure">
<Text Name="FailureHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureHeader)</Text>
<Text Name="FailureInstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Setup failed</Text>
<Text Name="FailureUninstallHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Uninstall failed</Text>
<Text Name="FailureRepairHeader" X="30" Y="110" Width="-30" Height="30" FontId="2" HideWhenDisabled="yes" DisablePrefix="yes">Repair failed</Text>
<Hypertext Name="FailureLogFileLink" X="30" Y="145" Width="-30" Height="50" FontId="3" TabStop="yes" HideWhenDisabled="yes">#(loc.FailureHyperlinkLogText)</Hypertext>
<Hypertext Name="FailureMessageText" X="30" Y="195" Width="-30" Height="50" FontId="3" TabStop="yes" HideWhenDisabled="yes" />
<Text Name="FailureRestartText" X="-30" Y="255" Width="400" Height="34" FontId="3" HideWhenDisabled="yes" DisablePrefix="yes">#(loc.FailureRestartText)</Text>
<Button Name="FailureRestartButton" X="90" Y="-100" Width="120" Height="50" TabStop="yes" FontId="0" HideWhenDisabled="yes">#(loc.FailureRestartButton)</Button>
<Button Name="FailureCloseButton" X="110" Y="-60" Width="80" Height="23" TabStop="yes" FontId="0">#(loc.FailureCloseButton)</Button>
</Page>
</Theme>

View File

@@ -15,6 +15,7 @@
"electron-rebuild": "1.5.11",
"file-loader": "0.9.0",
"font-awesome": "4.7.0",
"graceful-fs": "^4.1.11",
"html-loader": "0.4.4",
"json-loader": "0.5.4",
"less": "2.7.1",
@@ -23,6 +24,7 @@
"node-gyp": "^3.6.2",
"node-sass": "^4.5.3",
"npmlog": "4.1.0",
"npx": "^9.7.1",
"pug": "2.0.0-beta11",
"pug-html-loader": "1.0.9",
"pug-loader": "2.3.0",
@@ -41,7 +43,8 @@
"url-loader": "0.5.7",
"val-loader": "0.5.0",
"webpack": "^3.0.0",
"yaml-loader": "0.4.0"
"yaml-loader": "0.4.0",
"yarn": "^1.3.2"
},
"build": {
"appId": "org.terminus",
@@ -53,7 +56,9 @@
],
"win": {
"icon": "./build/windows/icon.ico",
"publish": ["github"]
"publish": [
"github"
]
},
"squirrelWindows": {
"iconUrl": "https://github.com/Eugeny/terminus/raw/master/build/windows/icon.ico",
@@ -63,7 +68,9 @@
"category": "public.app-category.video",
"icon": "./build/mac/icon.icns",
"identity": null,
"publish": ["github"]
"publish": [
"github"
]
},
"dmg": {
"artifactName": "terminus-${version}-${os}-${arch}.dmg"
@@ -71,7 +78,9 @@
"linux": {
"category": "Utilities",
"icon": "./build/icons",
"publish": ["github"]
"publish": [
"github"
]
},
"deb": {
"depends": [
@@ -82,6 +91,7 @@
"libappindicator1",
"libxtst6",
"libnss3",
"python-gnomekeyring",
"tmux"
],
"artifactName": "terminus-${version}-${os}-${arch}.deb"
@@ -94,7 +104,7 @@
}
},
"scripts": {
"build": "webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js",
"build": "webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
"watch": "webpack --progress --color --watch",
"start": "cross-env DEV=1 electron --js-flags='--ignition' app --debug",
"prod": "cross-env DEV=1 electron --js-flags='--ignition' app",

View File

@@ -3,7 +3,5 @@ const rebuild = require('electron-rebuild').default
const path = require('path')
const vars = require('./vars')
let buildPath = path.resolve(__dirname, '../terminus-terminal')
rebuild(buildPath, vars.electronVersion, process.arch, [], true).then(() => {
console.log('Done')
})
rebuild(path.resolve(__dirname, '../terminus-ssh'), vars.electronVersion, process.arch, [], true)
rebuild(path.resolve(__dirname, '../terminus-terminal'), vars.electronVersion, process.arch, [], true)

View File

@@ -4,16 +4,19 @@ const path = require('path')
const vars = require('./vars')
const log = require('npmlog')
const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
const npx = `${localBinPath}/npx`;
log.info('deps', 'app')
sh.exec('yarn install')
sh.exec(`${npx} yarn install`)
sh.cd('app')
sh.exec('yarn install')
sh.exec(`${npx} yarn install`)
sh.cd('..')
vars.builtinPlugins.forEach(plugin => {
log.info('deps', plugin)
sh.cd(plugin)
sh.exec('yarn install')
sh.exec(`${npx} yarn install`)
sh.cd('..')
})

View File

@@ -14,6 +14,7 @@ exports.builtinPlugins = [
'terminus-terminal',
'terminus-community-color-schemes',
'terminus-plugin-manager',
'terminus-ssh',
]
exports.nativeModules = ['node-pty-tmp', 'font-manager']
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain']
exports.electronVersion = pkgInfo.devDependencies.electron

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-community-color-schemes",
"version": "1.0.0-alpha.35",
"version": "1.0.0-alpha.36",
"description": "Community color schemes for Terminus",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -19,29 +19,19 @@ export class ColorSchemes extends TerminalColorSchemeProvider {
values[key] = value
})
let colors: string[] = []
let colorIndex = 0
while (values[`color${colorIndex}`]) {
colors.push(values[`color${colorIndex}`])
colorIndex++
}
schemes.push({
name: schemeFile.split('/')[1].trim(),
foreground: values.foreground,
background: values.background,
cursor: values.cursorColor,
colors: [
values.color0,
values.color1,
values.color2,
values.color3,
values.color4,
values.color5,
values.color6,
values.color7,
values.color8,
values.color9,
values.color10,
values.color11,
values.color12,
values.color13,
values.color14,
values.color15,
],
colors,
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-core",
"version": "1.0.0-alpha.35.1",
"version": "1.0.0-alpha.36",
"description": "Terminus core",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -170,7 +170,7 @@ export class AppRootComponent {
private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
let buttons: IToolbarButton[] = []
this.toolbarButtonProviders.forEach((provider) => {
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
buttons = buttons.concat(provider.provide())
})
return buttons

View File

@@ -1,6 +1,7 @@
import * as os from 'os'
import { Component, Inject } from '@angular/core'
import { ElectronService } from '../services/electron.service'
import { ConfigService } from '../services/config.service'
import { IToolbarButton, ToolbarButtonProvider } from '../api'
@Component({
@@ -13,13 +14,14 @@ export class StartPageComponent {
constructor (
private electron: ElectronService,
private config: ConfigService,
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
) {
this.version = electron.app.getVersion()
}
getButtons (): IToolbarButton[] {
return this.toolbarButtonProviders
return this.config.enabledServices(this.toolbarButtonProviders)
.map(provider => provider.provide())
.reduce((a, b) => a.concat(b))
.sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))

View File

@@ -10,9 +10,11 @@ hotkeys:
next-tab:
- 'Ctrl-Shift-ArrowRight'
- ['Ctrl-A', 'N']
- 'Ctrl-Tab'
previous-tab:
- 'Ctrl-Shift-ArrowLeft'
- ['Ctrl-A', 'P']
- 'Ctrl-Shift-Tab'
tab-1:
- 'Alt-1'
- ['Ctrl-A', '1']
@@ -43,3 +45,4 @@ hotkeys:
tab-10:
- 'Alt-0'
- ['Ctrl-A', '0']
pluginBlacklist: ['ssh']

View File

@@ -10,9 +10,11 @@ hotkeys:
next-tab:
- '⌘-ArrowRight'
- ['Ctrl-A', 'N']
- 'Ctrl-Tab'
previous-tab:
- '⌘-ArrowLeft'
- ['Ctrl-A', 'P']
- 'Ctrl-Shift-Tab'
tab-1:
- '⌘-1'
- ['Ctrl-A', '1']
@@ -43,3 +45,4 @@ hotkeys:
tab-10:
- '⌘-0'
- ['Ctrl-A', '0']
pluginBlacklist: ['ssh']

View File

@@ -10,9 +10,11 @@ hotkeys:
next-tab:
- 'Ctrl-Shift-ArrowRight'
- ['Ctrl-A', 'N']
- 'Ctrl-Tab'
previous-tab:
- 'Ctrl-Shift-ArrowLeft'
- ['Ctrl-A', 'P']
- 'Ctrl-Shift-Tab'
tab-1:
- 'Alt-1'
- ['Ctrl-A', '1']
@@ -43,3 +45,4 @@ hotkeys:
tab-10:
- 'Alt-0'
- ['Ctrl-A', '0']
pluginBlacklist: []

View File

@@ -6,6 +6,7 @@ import { Injectable, Inject } from '@angular/core'
import { ConfigProvider } from '../api/configProvider'
import { ElectronService } from './electron.service'
import { HostAppService } from './hostApp.service'
import * as Reflect from 'core-js/es7/reflect'
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
@@ -57,6 +58,7 @@ export class ConfigService {
private _store: any
private path: string
private defaults: any
private servicesCache: { [id: string]: Function[] } = null
constructor (
electron: ElectronService,
@@ -98,4 +100,28 @@ export class ConfigService {
requestRestart (): void {
this.restartRequested = true
}
enabledServices<T> (services: T[]): T[] {
if (!this.servicesCache) {
this.servicesCache = {}
let ngModule = Reflect.getMetadata('annotations', window['rootModule'])[0]
for (let imp of ngModule.imports) {
let module = imp['module'] || imp
let annotations = Reflect.getMetadata('annotations', module)
if (annotations) {
this.servicesCache[module['pluginName']] = annotations[0].providers.map(provider => {
return provider['useClass'] || provider
})
}
}
}
return services.filter(service => {
for (let pluginName in this.servicesCache) {
if (this.servicesCache[pluginName].includes(service.constructor)) {
return !this.store.pluginBlacklist.includes(pluginName)
}
}
return true
})
}
}

View File

@@ -42,7 +42,7 @@ export class HotkeysService {
}
})
})
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.hotkeyDescriptions = this.config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.config.changed$.subscribe(() => {
this.registerGlobalHotkey()
})

View File

@@ -14,7 +14,7 @@ const initializeWinston = (electron: ElectronService) => {
return new winston.Logger({
transports: [
new winston.transports.File({
level: 'info',
level: 'debug',
filename: path.join(logDirectory, 'log.txt'),
handleExceptions: false,
json: false,

View File

@@ -3,6 +3,7 @@ import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from '../services/log.service'
import { AppService } from '../services/app.service'
import { ConfigService } from '../services/config.service'
@Injectable()
export class TabRecoveryService {
@@ -11,6 +12,7 @@ export class TabRecoveryService {
constructor (
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
private app: AppService,
private config: ConfigService,
log: LogService
) {
this.logger = log.create('tabRecovery')
@@ -31,7 +33,7 @@ export class TabRecoveryService {
if (window.localStorage.tabsRecovery) {
let tabs: RecoveredTab[] = []
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
for (let provider of this.tabRecoveryProviders) {
for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) {
try {
let tab = await provider.recover(token)
if (tab) {

View File

@@ -17,7 +17,7 @@ export class ThemesService {
}
findTheme (name: string): Theme {
return this.themes.find(x => x.name === name)
return this.config.enabledServices(this.themes).find(x => x.name === name)
}
findCurrentTheme (): Theme {

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-plugin-manager",
"version": "1.0.0-alpha.35.1",
"version": "1.0.0-alpha.36",
"description": "Terminus' plugin manager",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -10,34 +10,23 @@ h3 Installed
.list-group
ng-container(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
.list-group-item.flex-column.align-items-start(*ngIf='knownUpgrades[plugin.name]')
.list-group-item.flex-column.align-items-start
.d-flex.w-100
.mr-auto.d-flex.flex-column
strong {{plugin.name}}
small.text-muted.mb-0((click)='showPluginInfo(plugin)') {{plugin.description}}
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
small {{plugin.description}}
.d-flex.flex-column.align-items-end.mr-3
div {{plugin.version}}
small.text-muted {{plugin.author}}
button.btn.btn-outline-primary(
*ngIf='npmInstalled',
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
(click)='upgradePlugin(plugin)',
[disabled]='busy[plugin.name] != undefined'
)
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
span Upgrade ({{knownUpgrades[plugin.name].version}})
ng-container(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
.list-group-item.flex-column.align-items-start(*ngIf='!knownUpgrades[plugin.name]')
.d-flex.w-100
.mr-auto.d-flex.flex-column
strong {{plugin.name}}
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
small {{plugin.description}}
.d-flex.flex-column.align-items-end.mr-3
div {{plugin.version}}
small.text-muted {{plugin.author}}
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
button.btn.btn-outline-danger(
(click)='uninstallPlugin(plugin)',
*ngIf='!plugin.isBuiltin && npmInstalled',
@@ -45,6 +34,16 @@ h3 Installed
)
i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
button.btn.btn-outline-danger(
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
(click)='enablePlugin(plugin)'
)
i.fa.fa-fw.fa-play
button.btn.btn-outline-primary(
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
(click)='disablePlugin(plugin)'
)
i.fa.fa-fw.fa-pause
.text-center.mt-5(*ngIf='npmMissing')
h4 npm not installed

View File

@@ -105,4 +105,16 @@ export class PluginsSettingsTabComponent {
showPluginInfo (plugin: IPluginInfo) {
this.electron.shell.openExternal('https://www.npmjs.com/package/' + plugin.packageName)
}
enablePlugin (plugin: IPluginInfo) {
this.config.store.pluginBlacklist = this.config.store.pluginBlacklist.filter(x => x !== plugin.name)
this.config.save()
this.config.requestRestart()
}
disablePlugin (plugin: IPluginInfo) {
this.config.store.pluginBlacklist.push(plugin.name)
this.config.save()
this.config.requestRestart()
}
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider, ComponentType } from 'terminus-settings'
import { SettingsTabProvider } from 'terminus-settings'
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
@@ -8,7 +8,7 @@ export class PluginsSettingsTabProvider extends SettingsTabProvider {
id = 'plugins'
title = 'Plugins'
getComponentType (): ComponentType {
getComponentType (): any {
return PluginsSettingsTabComponent
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-settings",
"version": "1.0.0-alpha.35.3",
"version": "1.0.0-alpha.36",
"description": "Terminus terminal settings page",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -1,12 +1,8 @@
import { Component } from '@angular/core'
export declare type ComponentType = new (...args: any[]) => Component
export abstract class SettingsTabProvider {
id: string
title: string
getComponentType (): ComponentType {
getComponentType (): any {
return null
}
}

View File

@@ -27,10 +27,12 @@ export class SettingsTabComponent extends BaseTabComponent {
@Inject(Theme) public themes: Theme[],
) {
super()
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.title = 'Settings'
this.scrollable = true
this.screens = this.docking.getScreens()
this.settingsProviders = config.enabledServices(this.settingsProviders)
this.themes = config.enabledServices(this.themes)
}
getRecoveryToken (): any {

47
terminus-ssh/package.json Normal file
View File

@@ -0,0 +1,47 @@
{
"name": "terminus-ssh",
"version": "0.0.1",
"description": "SSH connection manager for Terminus",
"keywords": [
"terminus-builtin-plugin"
],
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"build": "webpack --progress --color",
"watch": "webpack --progress --color --watch"
},
"files": [
"dist"
],
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@types/ssh2": "^0.5.35",
"@types/webpack-env": "^1.13.0",
"apply-loader": "^2.0.0",
"awesome-typescript-loader": "^3.1.2",
"electron": "^1.6.11",
"pug": "^2.0.0-rc.3",
"pug-loader": "^2.3.0",
"rxjs": "^5.4.0",
"typescript": "^2.2.2",
"webpack": "^2.3.3"
},
"peerDependencies": {
"@angular/common": "^4.1.3",
"@angular/core": "^4.1.3",
"@angular/forms": "^4.1.3",
"@ng-bootstrap/ng-bootstrap": "^1.0.0-alpha.29",
"terminus-core": "*",
"terminus-settings": "*",
"terminus-terminal": "*"
},
"optionalDependencies": {
"wincredmgr": "^2.0.0",
"xkeychain": "^0.0.6"
},
"dependencies": {
"ssh2": "^0.5.5"
}
}

51
terminus-ssh/src/api.ts Normal file
View File

@@ -0,0 +1,51 @@
import { BaseSession } from 'terminus-terminal'
export interface SSHConnection {
name?: string
host: string
user: string
password?: string
privateKey?: string
}
export class SSHSession extends BaseSession {
constructor (private shell: any) {
super()
this.open = true
this.shell.on('data', data => {
this.emitOutput(data.toString())
})
this.shell.on('end', () => {
if (this.open) {
this.destroy()
}
})
}
resize (columns, rows) {
this.shell.setWindow(rows, columns)
}
write (data) {
this.shell.write(data)
}
kill (signal?: string) {
this.shell.signal(signal || 'TERM')
}
async getChildProcesses (): Promise<any[]> {
return []
}
async gracefullyKillProcess (): Promise<void> {
this.kill('TERM')
}
async getWorkingDirectory (): Promise<string> {
return null
}
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { HotkeysService, ToolbarButtonProvider, IToolbarButton } from 'terminus-core'
import { SSHModalComponent } from './components/sshModal.component'
@Injectable()
export class ButtonProvider extends ToolbarButtonProvider {
constructor (
private ngbModal: NgbModal,
hotkeys: HotkeysService,
) {
super()
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
if (hotkey === 'ssh') {
this.activate()
}
})
}
activate () {
let modal = this.ngbModal.open(SSHModalComponent)
modal.result.then(() => {
//this.terminal.openTab(shell)
})
}
provide (): IToolbarButton[] {
return [{
icon: 'globe',
weight: 5,
title: 'SSH connections',
click: async () => {
this.activate()
}
}]
}
}

View File

@@ -0,0 +1,37 @@
.modal-body
.form-group
label Name
input.form-control(
type='text',
[(ngModel)]='connection.name',
)
.form-group
label Host
input.form-control(
type='text',
[(ngModel)]='connection.host',
)
.form-group
label Username
input.form-control(
type='text',
[(ngModel)]='connection.user',
)
.form-group
label Private key
.input-group
input.form-control(
type='text',
placeholder='Key file path',
[(ngModel)]='connection.privateKey'
)
.input-group-btn
button.btn.btn-secondary((click)='selectPrivateKey()')
i.fa.fa-folder-open
.modal-footer
button.btn.btn-outline-primary((click)='save()') Save
button.btn.btn-outline-danger((click)='cancel()') Cancel

View File

@@ -0,0 +1,37 @@
import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ElectronService, HostAppService } from 'terminus-core'
import { SSHConnection } from '../api'
@Component({
template: require('./editConnectionModal.component.pug'),
})
export class EditConnectionModalComponent {
connection: SSHConnection
constructor (
private modalInstance: NgbActiveModal,
private electron: ElectronService,
private hostApp: HostAppService,
) { }
selectPrivateKey () {
let path = this.electron.dialog.showOpenDialog(
this.hostApp.getWindow(),
{
title: 'Select private key',
}
)
if (path) {
this.connection.privateKey = path[0]
}
}
save () {
this.modalInstance.close(this.connection)
}
cancel () {
this.modalInstance.dismiss()
}
}

View File

@@ -0,0 +1,9 @@
.modal-body
input.form-control(
[type]='password ? "password" : "text"',
[(ngModel)]='value',
#input,
[placeholder]='prompt',
(keyup.enter)='ok()',
(keyup.esc)='cancel()',
)

View File

@@ -0,0 +1,27 @@
import { Component, Input, ViewChild, ElementRef } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
@Component({
template: require('./promptModal.component.pug'),
})
export class PromptModalComponent {
@Input() value: string
@Input() password: boolean
@ViewChild('input') input: ElementRef
constructor (
private modalInstance: NgbActiveModal,
) { }
ngOnInit () {
this.input.nativeElement.focus()
}
ok () {
this.modalInstance.close(this.value)
}
cancel () {
this.modalInstance.close('')
}
}

View File

@@ -0,0 +1,24 @@
.modal-body
input.form-control(
type='text',
[(ngModel)]='quickTarget',
autofocus,
placeholder='Quick connect: [user@]host',
(keyup.enter)='quickConnect()'
)
.list-group.mt-3(*ngIf='lastConnection')
a.list-group-item.list-group-item-action((click)='connect(lastConnection)')
i.fa.fa-fw.fa-history
span {{lastConnection.name}}
.list-group.mt-3
a.list-group-item.list-group-item-action(*ngFor='let connection of connections', (click)='connect(connection)')
i.fa.fa-fw.fa-globe
span {{connection.name}}
a.list-group-item.list-group-item-action((click)='manageConnections()')
i.fa.fa-fw.fa-wrench
span Manage connections
//.modal-footer
button.btn.btn-outline-primary((click)='close()') Cancel

View File

@@ -0,0 +1,60 @@
import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, AppService } from 'terminus-core'
import { SettingsTabComponent } from 'terminus-settings'
import { SSHService } from '../services/ssh.service'
import { SSHConnection } from '../api'
@Component({
template: require('./sshModal.component.pug'),
//styles: [require('./sshModal.component.scss')],
})
export class SSHModalComponent {
connections: SSHConnection[]
quickTarget: string
lastConnection: SSHConnection
constructor (
public modalInstance: NgbActiveModal,
private config: ConfigService,
private ssh: SSHService,
private app: AppService,
) { }
ngOnInit () {
this.connections = this.config.store.ssh.connections
if (window.localStorage.lastConnection) {
this.lastConnection = JSON.parse(window.localStorage.lastConnection)
}
}
quickConnect () {
let user = 'root'
let host = this.quickTarget
if (host.includes('@')) {
[user, host] = host.split('@')
}
let connection: SSHConnection = {
name: this.quickTarget,
host, user,
}
window.localStorage.lastConnection = JSON.stringify(connection)
this.connect(connection)
}
connect (connection: SSHConnection) {
this.close()
this.ssh.connect(connection).catch(error => {
alert(`Could not connect: ${error}`)
})
}
manageConnections () {
this.close()
this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' })
}
close () {
this.modalInstance.close()
}
}

View File

@@ -0,0 +1,15 @@
h3 Connections
.list-group.mt-3.mb-3
.list-group-item(*ngFor='let connection of connections')
.d-flex.w-100
.mr-auto
div
span {{connection.name}}
.text-muted {{connection.host}}
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
i.fa.fa-pencil
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
i.fa.fa-trash-o
button.btn.btn-outline-primary((click)='createConnection()') Add connection

View File

@@ -0,0 +1,51 @@
import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService } from 'terminus-core'
import { SSHConnection } from '../api'
import { EditConnectionModalComponent } from './editConnectionModal.component'
@Component({
template: require('./sshSettingsTab.component.pug'),
})
export class SSHSettingsTabComponent {
connections: SSHConnection[]
constructor (
public config: ConfigService,
private ngbModal: NgbModal,
) {
this.connections = this.config.store.ssh.connections
}
createConnection () {
let connection: SSHConnection = {
name: '',
host: '',
user: 'root',
}
let modal = this.ngbModal.open(EditConnectionModalComponent)
modal.componentInstance.connection = connection
modal.result.then(result => {
this.connections.push(result)
this.config.store.ssh.connections = this.connections
this.config.save()
})
}
editConnection (connection: SSHConnection) {
let modal = this.ngbModal.open(EditConnectionModalComponent)
modal.componentInstance.connection = Object.assign({}, connection)
modal.result.then(result => {
Object.assign(connection, result)
this.config.save()
})
}
deleteConnection (connection: SSHConnection) {
if (confirm(`Delete "${connection.name}"?`)) {
this.connections = this.connections.filter(x => x !== connection)
this.config.store.ssh.connections = this.connections
this.config.save()
}
}
}

View File

@@ -0,0 +1,18 @@
import { ConfigProvider } from 'terminus-core'
export class SSHConfigProvider extends ConfigProvider {
defaults = {
ssh: {
connections: [],
options: {
}
},
hotkeys: {
'ssh': [
'Alt-S',
],
},
}
platformDefaults = { }
}

43
terminus-ssh/src/index.ts Normal file
View File

@@ -0,0 +1,43 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
import { SettingsTabProvider } from 'terminus-settings'
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
import { SSHModalComponent } from './components/sshModal.component'
import { PromptModalComponent } from './components/promptModal.component'
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
import { SSHService } from './services/ssh.service'
import { ButtonProvider } from './buttonProvider'
import { SSHConfigProvider } from './config'
import { SSHSettingsTabProvider } from './settings'
@NgModule({
imports: [
NgbModule,
CommonModule,
FormsModule,
],
providers: [
SSHService,
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },
],
entryComponents: [
EditConnectionModalComponent,
PromptModalComponent,
SSHModalComponent,
SSHSettingsTabComponent,
],
declarations: [
EditConnectionModalComponent,
PromptModalComponent,
SSHModalComponent,
SSHSettingsTabComponent,
],
})
export default class SSHModule { }

View File

@@ -0,0 +1,194 @@
import { Injectable, NgZone } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Client } from 'ssh2'
import * as fs from 'mz/fs'
import * as path from 'path'
import { AppService, HostAppService, Platform } from 'terminus-core'
import { TerminalTabComponent } from 'terminus-terminal'
import { SSHConnection, SSHSession } from '../api'
import { PromptModalComponent } from '../components/promptModal.component'
const { SSH2Stream } = require('ssh2-streams')
let xkeychain
let wincredmgr
try {
xkeychain = require('xkeychain')
} catch (error) {
try {
wincredmgr = require('wincredmgr')
} catch (error2) {
console.warn('No keychain manager available')
}
}
@Injectable()
export class SSHService {
constructor (
private app: AppService,
private zone: NgZone,
private ngbModal: NgbModal,
private hostApp: HostAppService,
) {
}
savePassword (connection: SSHConnection, password: string) {
if (xkeychain) {
xkeychain.setPassword({
account: connection.user,
service: `ssh@${connection.host}`,
password
}, () => null)
} else {
wincredmgr.WriteCredentials(
'user',
password,
`ssh:${connection.user}@${connection.host}`,
)
}
}
deletePassword (connection: SSHConnection) {
if (xkeychain) {
xkeychain.deletePassword({
account: connection.user,
service: `ssh@${connection.host}`,
}, () => null)
} else {
wincredmgr.DeleteCredentials(
`ssh:${connection.user}@${connection.host}`,
)
}
}
loadPassword (connection: SSHConnection): Promise<string> {
return new Promise(resolve => {
if (xkeychain) {
xkeychain.getPassword({
account: connection.user,
service: `ssh@${connection.host}`,
}, (_, result) => resolve(result))
} else {
try {
resolve(wincredmgr.ReadCredentials(`ssh:${connection.user}@${connection.host}`).password)
} catch (error) {
resolve(null)
}
}
})
}
async connect (connection: SSHConnection): Promise<TerminalTabComponent> {
let privateKey: string = null
let keyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
if (!connection.privateKey && await fs.exists(keyPath)) {
connection.privateKey = keyPath
}
if (connection.privateKey) {
try {
privateKey = (await fs.readFile(connection.privateKey)).toString()
} catch (error) { }
}
let ssh = new Client()
let connected = false
let savedPassword: string = null
await new Promise((resolve, reject) => {
ssh.on('ready', () => {
connected = true
if (savedPassword) {
this.savePassword(connection, savedPassword)
}
this.zone.run(resolve)
})
ssh.on('error', error => {
this.deletePassword(connection)
this.zone.run(() => {
if (connected) {
alert(`SSH error: ${error}`)
} else {
reject(error)
}
})
})
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
console.log(name, instructions, instructionsLang)
let results = []
for (let prompt of prompts) {
let modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = prompt.prompt
modal.componentInstance.password = !prompt.echo
results.push(await modal.result)
}
finish(results)
}))
let agent: string = null
if (this.hostApp.platform === Platform.Windows) {
agent = 'pageant'
} else {
agent = process.env.SSH_AUTH_SOCK
}
ssh.connect({
host: connection.host,
username: connection.user,
password: privateKey ? undefined : '',
privateKey,
tryKeyboard: true,
agent,
agentForward: !!agent,
})
let keychainPasswordUsed = false
;(ssh as any).config.password = () => this.zone.run(async () => {
if (connection.password) {
return connection.password
}
if (!keychainPasswordUsed && (wincredmgr || xkeychain.isSupported())) {
let password = await this.loadPassword(connection)
if (password) {
keychainPasswordUsed = true
return password
}
}
let modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = `Password for ${connection.user}@${connection.host}`
modal.componentInstance.password = true
savedPassword = await modal.result
return savedPassword
})
})
try {
let shell = await new Promise((resolve, reject) => {
ssh.shell({ term: 'xterm-256color' }, (err, shell) => {
if (err) {
reject(err)
} else {
resolve(shell)
}
})
})
let session = new SSHSession(shell)
return this.zone.run(() => this.app.openNewTab(
TerminalTabComponent,
{ session, sessionOptions: {} }
) as TerminalTabComponent)
} catch (error) {
console.log(error)
throw error
}
}
}
const _authPassword = SSH2Stream.prototype.authPassword
SSH2Stream.prototype.authPassword = async function (username, passwordFn) {
_authPassword.bind(this)(username, await passwordFn())
}

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider } from 'terminus-settings'
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
@Injectable()
export class SSHSettingsTabProvider extends SettingsTabProvider {
id = 'ssh'
title = 'SSH'
getComponentType (): any {
return SSHSettingsTabComponent
}
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"exclude": ["node_modules", "dist"],
"compilerOptions": {
"baseUrl": "src",
"declarationDir": "dist",
"paths": {
"terminus-*": ["terminus-*"],
"*": ["app/node_modules/*"]
}
}
}

View File

@@ -0,0 +1,48 @@
const path = require('path')
module.exports = {
target: 'node',
entry: 'src/index.ts',
devtool: 'source-map',
context: __dirname,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
pathinfo: true,
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
},
resolve: {
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
extensions: ['.ts', '.js'],
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader',
query: {
configFileName: path.resolve(__dirname, 'tsconfig.json'),
typeRoots: [path.resolve(__dirname, 'node_modules/@types')],
paths: {
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
"*": [path.resolve(__dirname, '../app/node_modules/*')],
}
}
},
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
]
},
externals: [
'fs',
'node-ssh',
'xkeychain',
'wincredmgr',
'path',
/^rxjs/,
/^@angular/,
/^@ng-bootstrap/,
/^terminus-/,
]
}

2959
terminus-ssh/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "terminus-terminal",
"version": "1.0.0-alpha.35.1",
"version": "1.0.0-alpha.36",
"description": "Terminus' terminal emulation core",
"keywords": [
"terminus-builtin-plugin"

View File

@@ -27,7 +27,7 @@ export class TerminalSettingsTabComponent {
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
@Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
) {
this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable())
this.persistenceProviders = this.config.enabledServices(persistenceProviders).filter(x => x.isAvailable())
}
async ngOnInit () {
@@ -46,8 +46,8 @@ export class TerminalSettingsTabComponent {
this.fonts.sort()
})
}
this.colorSchemes = (await Promise.all(this.colorSchemeProviders.map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
this.shells = (await Promise.all(this.shellProviders.map(x => x.provide()))).reduce((a, b) => a.concat(b))
this.colorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
this.shells = (await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))).reduce((a, b) => a.concat(b))
}
fontAutocomplete = (text$: Observable<string>) => {

View File

@@ -125,7 +125,7 @@ export class TerminalTabComponent extends BaseTabComponent {
})
this.hterm = new hterm.hterm.Terminal()
this.decorators.forEach((decorator) => {
this.config.enabledServices(this.decorators).forEach((decorator) => {
decorator.attach(this)
})
@@ -406,7 +406,7 @@ export class TerminalTabComponent extends BaseTabComponent {
}
ngOnDestroy () {
this.decorators.forEach(decorator => {
this.config.enabledServices(this.decorators).forEach(decorator => {
decorator.detach(this)
})
this.hotkeysSubscription.unsubscribe()

View File

@@ -153,8 +153,8 @@ export class TMux {
async create (id: string, options: SessionOptions): Promise<void> {
await this.ready
let args = [options.command].concat(options.args)
let cmd = args.map(x => `"${x.replace('"', '\\"')}"`)
let args = [options.command].concat(options.args.slice(1))
let cmd = args.map(x => `"${x.replace('"', '\\"')}"`).join(' ')
await this.process.command(
`new-session -s "${id}" -d`
+ (options.cwd ? ` -c '${options.cwd.replace("'", "\\'")}'` : '')

View File

@@ -202,7 +202,7 @@ export class SessionsService {
) {
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty-tmp', global as any)
this.logger = log.create('sessions')
this.persistenceProviders = this.persistenceProviders.filter(x => x.isAvailable())
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
}
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {

View File

@@ -27,7 +27,7 @@ export class TerminalService {
async reloadShells () {
this.shells$ = new AsyncSubject<IShell[]>()
let shellLists = await Promise.all(this.shellProviders.map(x => x.provide()))
let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))
this.shells$.next(shellLists.reduce((a, b) => a.concat(b)))
this.shells$.complete()
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider, ComponentType } from 'terminus-settings'
import { SettingsTabProvider } from 'terminus-settings'
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
@@ -8,7 +8,7 @@ export class TerminalSettingsTabProvider extends SettingsTabProvider {
id = 'terminal'
title = 'Terminal'
getComponentType (): ComponentType {
getComponentType (): any {
return TerminalSettingsTabComponent
}
}

View File

@@ -34,7 +34,14 @@ export class WindowsStockShellsProvider extends ShellProvider {
]
},
{ id: 'cmd', name: 'CMD (stock)', command: 'cmd.exe' },
{ id: 'powershell', name: 'PowerShell', command: 'powershell.exe' },
{
id: 'powershell',
name: 'PowerShell',
command: 'powershell.exe',
env: {
TERM: 'cygwin',
}
},
]
}
}

View File

@@ -5,4 +5,5 @@ module.exports = [
require('./terminus-terminal/webpack.config.js'),
require('./terminus-community-color-schemes/webpack.config.js'),
require('./terminus-plugin-manager/webpack.config.js'),
require('./terminus-ssh/webpack.config.js'),
]

1306
yarn.lock

File diff suppressed because it is too large Load Diff