mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
New Connections Configs (#1383)
This adds the following connections changes: - connections can be hidden from the dropdown in our internal connections.json config - `wsh ssh` -i will write identity files to the internal connections.json config for that connection - the internal connections.json config will also be used to get identity files when connecting - the internal connections.json config allows setting theme, fontsize, and font for specific connections - successful connections (including those using wsh ssh) are saved to the internal connections.json config - the connections.json config will be used to help pre-populate the dropdown list - adds an item to the dropdown to edit the connections config in an ephemeral block --------- Co-authored-by: Evan Simkowitz <esimkowitz@users.noreply.github.com>
This commit is contained in:
parent
5c315779ba
commit
b4b0222c9d
@ -15,6 +15,8 @@ import { TypeAheadModal } from "@/app/modals/typeaheadmodal";
|
|||||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||||
import {
|
import {
|
||||||
atoms,
|
atoms,
|
||||||
|
createBlock,
|
||||||
|
getApi,
|
||||||
getBlockComponentModel,
|
getBlockComponentModel,
|
||||||
getConnStatusAtom,
|
getConnStatusAtom,
|
||||||
getHostName,
|
getHostName,
|
||||||
@ -182,6 +184,9 @@ const BlockFrame_Header = ({
|
|||||||
const prevMagifiedState = React.useRef(magnified);
|
const prevMagifiedState = React.useRef(magnified);
|
||||||
const manageConnection = util.useAtomValueSafe(viewModel?.manageConnection);
|
const manageConnection = util.useAtomValueSafe(viewModel?.manageConnection);
|
||||||
const dragHandleRef = preview ? null : nodeModel.dragHandleRef;
|
const dragHandleRef = preview ? null : nodeModel.dragHandleRef;
|
||||||
|
const connName = blockData?.meta?.connection;
|
||||||
|
const allSettings = jotai.useAtomValue(atoms.fullConfigAtom);
|
||||||
|
const wshEnabled = allSettings?.connections?.[connName]?.["conn:wshenabled"] ?? true;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!magnified || preview || prevMagifiedState.current) {
|
if (!magnified || preview || prevMagifiedState.current) {
|
||||||
@ -239,6 +244,11 @@ const BlockFrame_Header = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const wshInstallButton: IconButtonDecl = {
|
||||||
|
elemtype: "iconbutton",
|
||||||
|
icon: "link-slash",
|
||||||
|
title: "wsh is not installed for this connection",
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block-frame-default-header" ref={dragHandleRef} onContextMenu={onContextMenu}>
|
<div className="block-frame-default-header" ref={dragHandleRef} onContextMenu={onContextMenu}>
|
||||||
@ -256,6 +266,9 @@ const BlockFrame_Header = ({
|
|||||||
changeConnModalAtom={changeConnModalAtom}
|
changeConnModalAtom={changeConnModalAtom}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{manageConnection && !wshEnabled && (
|
||||||
|
<IconButton decl={wshInstallButton} className="block-frame-header-iconbutton" />
|
||||||
|
)}
|
||||||
<div className="block-frame-textelems-wrapper">{headerTextElems}</div>
|
<div className="block-frame-textelems-wrapper">{headerTextElems}</div>
|
||||||
<div className="block-frame-end-icons">{endIconsElem}</div>
|
<div className="block-frame-end-icons">{endIconsElem}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -568,6 +581,10 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
const allConnStatus = jotai.useAtomValue(atoms.allConnStatus);
|
const allConnStatus = jotai.useAtomValue(atoms.allConnStatus);
|
||||||
const [rowIndex, setRowIndex] = React.useState(0);
|
const [rowIndex, setRowIndex] = React.useState(0);
|
||||||
const connStatusMap = new Map<string, ConnStatus>();
|
const connStatusMap = new Map<string, ConnStatus>();
|
||||||
|
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
|
||||||
|
const connectionsConfig = fullConfig.connections;
|
||||||
|
let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) || true;
|
||||||
|
|
||||||
let maxActiveConnNum = 1;
|
let maxActiveConnNum = 1;
|
||||||
for (const conn of allConnStatus) {
|
for (const conn of allConnStatus) {
|
||||||
if (conn.activeconnnum > maxActiveConnNum) {
|
if (conn.activeconnnum > maxActiveConnNum) {
|
||||||
@ -638,7 +655,12 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
if (conn === connSelected) {
|
if (conn === connSelected) {
|
||||||
createNew = false;
|
createNew = false;
|
||||||
}
|
}
|
||||||
if (conn.includes(connSelected)) {
|
if (
|
||||||
|
conn.includes(connSelected) &&
|
||||||
|
connectionsConfig[conn]?.["display:hidden"] != true &&
|
||||||
|
(connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh)
|
||||||
|
// != false is necessary because of defaults
|
||||||
|
) {
|
||||||
filteredList.push(conn);
|
filteredList.push(conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -647,7 +669,12 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
if (conn === connSelected) {
|
if (conn === connSelected) {
|
||||||
createNew = false;
|
createNew = false;
|
||||||
}
|
}
|
||||||
if (conn.includes(connSelected)) {
|
if (
|
||||||
|
conn.includes(connSelected) &&
|
||||||
|
connectionsConfig[conn]?.["display:hidden"] != true &&
|
||||||
|
(connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh)
|
||||||
|
// != false is necessary because of defaults
|
||||||
|
) {
|
||||||
filteredWslList.push(conn);
|
filteredWslList.push(conn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -734,9 +761,38 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
const connectionsEditItem: SuggestionConnectionItem = {
|
||||||
|
status: "disconnected",
|
||||||
|
icon: "gear",
|
||||||
|
iconColor: "var(--grey-text-color",
|
||||||
|
value: "Edit Connections",
|
||||||
|
label: "Edit Connections",
|
||||||
|
onSelect: () => {
|
||||||
|
util.fireAndForget(async () => {
|
||||||
|
globalStore.set(changeConnModalAtom, false);
|
||||||
|
const path = `${getApi().getConfigDir()}/connections.json`;
|
||||||
|
const blockDef: BlockDef = {
|
||||||
|
meta: {
|
||||||
|
view: "preview",
|
||||||
|
file: path,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await createBlock(blockDef, false, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const sortedRemoteItems = remoteItems.sort(
|
||||||
|
(itemA: SuggestionConnectionItem, itemB: SuggestionConnectionItem) => {
|
||||||
|
const connNameA = itemA.value;
|
||||||
|
const connNameB = itemB.value;
|
||||||
|
const valueA = connectionsConfig[connNameA]?.["display:order"] ?? 0;
|
||||||
|
const valueB = connectionsConfig[connNameB]?.["display:order"] ?? 0;
|
||||||
|
return valueA - valueB;
|
||||||
|
}
|
||||||
|
);
|
||||||
const remoteSuggestions: SuggestionConnectionScope = {
|
const remoteSuggestions: SuggestionConnectionScope = {
|
||||||
headerText: "Remote",
|
headerText: "Remote",
|
||||||
items: remoteItems,
|
items: [...sortedRemoteItems, connectionsEditItem],
|
||||||
};
|
};
|
||||||
|
|
||||||
let suggestions: Array<SuggestionsType> = [];
|
let suggestions: Array<SuggestionsType> = [];
|
||||||
|
@ -246,6 +246,21 @@ function useBlockMetaKeyAtom<T extends keyof MetaType>(blockId: string, key: T):
|
|||||||
return useAtomValue(getBlockMetaKeyAtom(blockId, key));
|
return useAtomValue(getBlockMetaKeyAtom(blockId, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConnConfigKeyAtom<T extends keyof ConnKeywords>(connName: string, key: T): Atom<ConnKeywords[T]> {
|
||||||
|
let connCache = getSingleConnAtomCache(connName);
|
||||||
|
const keyAtomName = "#conn-" + key;
|
||||||
|
let keyAtom = connCache.get(keyAtomName);
|
||||||
|
if (keyAtom != null) {
|
||||||
|
return keyAtom;
|
||||||
|
}
|
||||||
|
keyAtom = atom((get) => {
|
||||||
|
let fullConfig = get(atoms.fullConfigAtom);
|
||||||
|
return fullConfig.connections[connName]?.[key];
|
||||||
|
});
|
||||||
|
connCache.set(keyAtomName, keyAtom);
|
||||||
|
return keyAtom;
|
||||||
|
}
|
||||||
|
|
||||||
const settingsAtomCache = new Map<string, Atom<any>>();
|
const settingsAtomCache = new Map<string, Atom<any>>();
|
||||||
|
|
||||||
function getOverrideConfigAtom<T extends keyof SettingsType>(blockId: string, key: T): Atom<SettingsType[T]> {
|
function getOverrideConfigAtom<T extends keyof SettingsType>(blockId: string, key: T): Atom<SettingsType[T]> {
|
||||||
@ -261,6 +276,13 @@ function getOverrideConfigAtom<T extends keyof SettingsType>(blockId: string, ke
|
|||||||
if (metaKeyVal != null) {
|
if (metaKeyVal != null) {
|
||||||
return metaKeyVal;
|
return metaKeyVal;
|
||||||
}
|
}
|
||||||
|
const connNameAtom = getBlockMetaKeyAtom(blockId, "connection");
|
||||||
|
const connName = get(connNameAtom);
|
||||||
|
const connConfigKeyAtom = getConnConfigKeyAtom(connName, key as any);
|
||||||
|
const connConfigKeyVal = get(connConfigKeyAtom);
|
||||||
|
if (connConfigKeyVal != null) {
|
||||||
|
return connConfigKeyVal;
|
||||||
|
}
|
||||||
const settingsKeyAtom = getSettingsKeyAtom(key);
|
const settingsKeyAtom = getSettingsKeyAtom(key);
|
||||||
const settingsVal = get(settingsKeyAtom);
|
const settingsVal = get(settingsKeyAtom);
|
||||||
if (settingsVal != null) {
|
if (settingsVal != null) {
|
||||||
@ -322,6 +344,15 @@ function getSingleBlockAtomCache(blockId: string): Map<string, Atom<any>> {
|
|||||||
return blockCache;
|
return blockCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSingleConnAtomCache(connName: string): Map<string, Atom<any>> {
|
||||||
|
let blockCache = blockAtomCache.get(connName);
|
||||||
|
if (blockCache == null) {
|
||||||
|
blockCache = new Map<string, Atom<any>>();
|
||||||
|
blockAtomCache.set(connName, blockCache);
|
||||||
|
}
|
||||||
|
return blockCache;
|
||||||
|
}
|
||||||
|
|
||||||
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => Atom<T>): Atom<T> {
|
function useBlockAtom<T>(blockId: string, name: string, makeFn: () => Atom<T>): Atom<T> {
|
||||||
const blockCache = getSingleBlockAtomCache(blockId);
|
const blockCache = getSingleBlockAtomCache(blockId);
|
||||||
let atom = blockCache.get(name);
|
let atom = blockCache.get(name);
|
||||||
|
@ -121,6 +121,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
loadableSpecializedView: Atom<Loadable<{ specializedView?: string; errorStr?: string }>>;
|
loadableSpecializedView: Atom<Loadable<{ specializedView?: string; errorStr?: string }>>;
|
||||||
manageConnection: Atom<boolean>;
|
manageConnection: Atom<boolean>;
|
||||||
connStatus: Atom<ConnStatus>;
|
connStatus: Atom<ConnStatus>;
|
||||||
|
filterOutNowsh?: Atom<boolean>;
|
||||||
|
|
||||||
metaFilePath: Atom<string>;
|
metaFilePath: Atom<string>;
|
||||||
statFilePath: Atom<Promise<string>>;
|
statFilePath: Atom<Promise<string>>;
|
||||||
@ -164,6 +165,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
this.manageConnection = atom(true);
|
this.manageConnection = atom(true);
|
||||||
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
||||||
this.markdownShowToc = atom(false);
|
this.markdownShowToc = atom(false);
|
||||||
|
this.filterOutNowsh = atom(true);
|
||||||
this.monacoRef = createRef();
|
this.monacoRef = createRef();
|
||||||
this.viewIcon = atom((get) => {
|
this.viewIcon = atom((get) => {
|
||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
|
@ -91,7 +91,7 @@ function convertWaveEventToDataItem(event: WaveEvent): DataItem {
|
|||||||
return dataItem;
|
return dataItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SysinfoViewModel {
|
class SysinfoViewModel implements ViewModel {
|
||||||
viewType: string;
|
viewType: string;
|
||||||
blockAtom: jotai.Atom<Block>;
|
blockAtom: jotai.Atom<Block>;
|
||||||
termMode: jotai.Atom<string>;
|
termMode: jotai.Atom<string>;
|
||||||
@ -109,6 +109,7 @@ class SysinfoViewModel {
|
|||||||
metrics: jotai.Atom<string[]>;
|
metrics: jotai.Atom<string[]>;
|
||||||
connection: jotai.Atom<string>;
|
connection: jotai.Atom<string>;
|
||||||
manageConnection: jotai.Atom<boolean>;
|
manageConnection: jotai.Atom<boolean>;
|
||||||
|
filterOutNowsh: jotai.Atom<boolean>;
|
||||||
connStatus: jotai.Atom<ConnStatus>;
|
connStatus: jotai.Atom<ConnStatus>;
|
||||||
plotMetaAtom: jotai.PrimitiveAtom<Map<string, TimeSeriesMeta>>;
|
plotMetaAtom: jotai.PrimitiveAtom<Map<string, TimeSeriesMeta>>;
|
||||||
endIconButtons: jotai.Atom<IconButtonDecl[]>;
|
endIconButtons: jotai.Atom<IconButtonDecl[]>;
|
||||||
@ -176,6 +177,7 @@ class SysinfoViewModel {
|
|||||||
});
|
});
|
||||||
this.plotMetaAtom = jotai.atom(new Map(Object.entries(DefaultPlotMeta)));
|
this.plotMetaAtom = jotai.atom(new Map(Object.entries(DefaultPlotMeta)));
|
||||||
this.manageConnection = jotai.atom(true);
|
this.manageConnection = jotai.atom(true);
|
||||||
|
this.filterOutNowsh = jotai.atom(true);
|
||||||
this.loadingAtom = jotai.atom(true);
|
this.loadingAtom = jotai.atom(true);
|
||||||
this.numPoints = jotai.atom((get) => {
|
this.numPoints = jotai.atom((get) => {
|
||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
|
@ -41,7 +41,7 @@ type InitialLoadDataType = {
|
|||||||
heldData: Uint8Array[];
|
heldData: Uint8Array[];
|
||||||
};
|
};
|
||||||
|
|
||||||
class TermViewModel {
|
class TermViewModel implements ViewModel {
|
||||||
viewType: string;
|
viewType: string;
|
||||||
nodeModel: BlockNodeModel;
|
nodeModel: BlockNodeModel;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
@ -54,6 +54,7 @@ class TermViewModel {
|
|||||||
viewText: jotai.Atom<HeaderElem[]>;
|
viewText: jotai.Atom<HeaderElem[]>;
|
||||||
blockBg: jotai.Atom<MetaType>;
|
blockBg: jotai.Atom<MetaType>;
|
||||||
manageConnection: jotai.Atom<boolean>;
|
manageConnection: jotai.Atom<boolean>;
|
||||||
|
filterOutNowsh?: jotai.Atom<boolean>;
|
||||||
connStatus: jotai.Atom<ConnStatus>;
|
connStatus: jotai.Atom<ConnStatus>;
|
||||||
termWshClient: TermWshClient;
|
termWshClient: TermWshClient;
|
||||||
vdomBlockId: jotai.Atom<string>;
|
vdomBlockId: jotai.Atom<string>;
|
||||||
@ -196,6 +197,7 @@ class TermViewModel {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
this.filterOutNowsh = jotai.atom(false);
|
||||||
this.termThemeNameAtom = useBlockAtom(blockId, "termthemeatom", () => {
|
this.termThemeNameAtom = useBlockAtom(blockId, "termthemeatom", () => {
|
||||||
return jotai.atom<string>((get) => {
|
return jotai.atom<string>((get) => {
|
||||||
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
|
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
|
||||||
@ -221,7 +223,10 @@ class TermViewModel {
|
|||||||
const blockData = get(this.blockAtom);
|
const blockData = get(this.blockAtom);
|
||||||
const fsSettingsAtom = getSettingsKeyAtom("term:fontsize");
|
const fsSettingsAtom = getSettingsKeyAtom("term:fontsize");
|
||||||
const settingsFontSize = get(fsSettingsAtom);
|
const settingsFontSize = get(fsSettingsAtom);
|
||||||
const rtnFontSize = blockData?.meta?.["term:fontsize"] ?? settingsFontSize ?? 12;
|
const connName = blockData?.meta?.connection;
|
||||||
|
const fullConfig = get(atoms.fullConfigAtom);
|
||||||
|
const connFontSize = fullConfig?.connections?.[connName]?.["term:fontsize"];
|
||||||
|
const rtnFontSize = blockData?.meta?.["term:fontsize"] ?? connFontSize ?? settingsFontSize ?? 12;
|
||||||
if (typeof rtnFontSize != "number" || isNaN(rtnFontSize) || rtnFontSize < 4 || rtnFontSize > 64) {
|
if (typeof rtnFontSize != "number" || isNaN(rtnFontSize) || rtnFontSize < 4 || rtnFontSize > 64) {
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
@ -725,6 +730,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
const termModeRef = React.useRef(termMode);
|
const termModeRef = React.useRef(termMode);
|
||||||
|
|
||||||
const termFontSize = jotai.useAtomValue(model.fontSizeAtom);
|
const termFontSize = jotai.useAtomValue(model.fontSizeAtom);
|
||||||
|
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||||
|
const connFontFamily = fullConfig.connections?.[blockData?.meta?.connection]?.["term:fontfamily"];
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||||
@ -750,7 +757,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
{
|
{
|
||||||
theme: termTheme,
|
theme: termTheme,
|
||||||
fontSize: termFontSize,
|
fontSize: termFontSize,
|
||||||
fontFamily: termSettings?.["term:fontfamily"] ?? "Hack",
|
fontFamily: termSettings?.["term:fontfamily"] ?? connFontFamily ?? "Hack",
|
||||||
drawBoldTextInBrightColors: false,
|
drawBoldTextInBrightColors: false,
|
||||||
fontWeight: "normal",
|
fontWeight: "normal",
|
||||||
fontWeightBold: "bold",
|
fontWeightBold: "bold",
|
||||||
@ -784,7 +791,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
termWrap.dispose();
|
termWrap.dispose();
|
||||||
rszObs.disconnect();
|
rszObs.disconnect();
|
||||||
};
|
};
|
||||||
}, [blockId, termSettings, termFontSize]);
|
}, [blockId, termSettings, termFontSize, connFontFamily]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (termModeRef.current == "vdom" && termMode == "term") {
|
if (termModeRef.current == "vdom" && termMode == "term") {
|
||||||
|
1
frontend/types/custom.d.ts
vendored
1
frontend/types/custom.d.ts
vendored
@ -237,6 +237,7 @@ declare global {
|
|||||||
blockBg?: jotai.Atom<MetaType>;
|
blockBg?: jotai.Atom<MetaType>;
|
||||||
manageConnection?: jotai.Atom<boolean>;
|
manageConnection?: jotai.Atom<boolean>;
|
||||||
noPadding?: jotai.Atom<boolean>;
|
noPadding?: jotai.Atom<boolean>;
|
||||||
|
filterOutNowsh?: jotai.Atom<boolean>;
|
||||||
|
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
onForward?: () => void;
|
onForward?: () => void;
|
||||||
|
10
frontend/types/gotypes.d.ts
vendored
10
frontend/types/gotypes.d.ts
vendored
@ -277,8 +277,14 @@ declare global {
|
|||||||
|
|
||||||
// wshrpc.ConnKeywords
|
// wshrpc.ConnKeywords
|
||||||
type ConnKeywords = {
|
type ConnKeywords = {
|
||||||
wshenabled?: boolean;
|
"conn:wshenabled"?: boolean;
|
||||||
askbeforewshinstall?: boolean;
|
"conn:askbeforewshinstall"?: boolean;
|
||||||
|
"display:hidden"?: boolean;
|
||||||
|
"display:order"?: number;
|
||||||
|
"term:*"?: boolean;
|
||||||
|
"term:fontsize"?: number;
|
||||||
|
"term:fontfamily"?: string;
|
||||||
|
"term:theme"?: string;
|
||||||
"ssh:user"?: string;
|
"ssh:user"?: string;
|
||||||
"ssh:hostname"?: string;
|
"ssh:hostname"?: string;
|
||||||
"ssh:port"?: string;
|
"ssh:port"?: string;
|
||||||
|
@ -342,7 +342,7 @@ func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName s
|
|||||||
}
|
}
|
||||||
if !response.Confirm {
|
if !response.Confirm {
|
||||||
meta := make(map[string]any)
|
meta := make(map[string]any)
|
||||||
meta["wshenabled"] = false
|
meta["conn:wshenabled"] = false
|
||||||
err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta)
|
err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("warning: error writing to connections file: %v", err)
|
log.Printf("warning: error writing to connections file: %v", err)
|
||||||
@ -454,9 +454,41 @@ func (conn *SSHConn) Connect(ctx context.Context, connFlags *wshrpc.ConnKeywords
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
conn.FireConnChangeEvent()
|
conn.FireConnChangeEvent()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logic for saving connection and potential flags (we only save once a connection has been made successfully)
|
||||||
|
// at the moment, identity files is the only saved flag
|
||||||
|
var identityFiles []string
|
||||||
|
existingConfig := wconfig.ReadFullConfig()
|
||||||
|
existingConnection, ok := existingConfig.Connections[conn.GetName()]
|
||||||
|
if ok {
|
||||||
|
identityFiles = existingConnection.SshIdentityFile
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// i do not consider this a critical failure
|
||||||
|
log.Printf("config read error: unable to save connection %s: %v", conn.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := make(map[string]any)
|
||||||
|
if connFlags.SshIdentityFile != nil {
|
||||||
|
for _, identityFile := range connFlags.SshIdentityFile {
|
||||||
|
if utilfn.ContainsStr(identityFiles, identityFile) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
identityFiles = append(identityFiles, connFlags.SshIdentityFile...)
|
||||||
|
}
|
||||||
|
meta["ssh:identityfile"] = identityFiles
|
||||||
|
}
|
||||||
|
err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta)
|
||||||
|
if err != nil {
|
||||||
|
// i do not consider this a critical failure
|
||||||
|
log.Printf("config write error: unable to save connection %s: %v", conn.GetName(), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (conn *SSHConn) WithLock(fn func()) {
|
func (conn *SSHConn) WithLock(fn func()) {
|
||||||
conn.Lock.Lock()
|
conn.Lock.Lock()
|
||||||
defer conn.Lock.Unlock()
|
defer conn.Lock.Unlock()
|
||||||
@ -484,11 +516,11 @@ func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.Conn
|
|||||||
askBeforeInstall := config.Settings.ConnAskBeforeWshInstall
|
askBeforeInstall := config.Settings.ConnAskBeforeWshInstall
|
||||||
connSettings, ok := config.Connections[conn.GetName()]
|
connSettings, ok := config.Connections[conn.GetName()]
|
||||||
if ok {
|
if ok {
|
||||||
if connSettings.WshEnabled != nil {
|
if connSettings.ConnWshEnabled != nil {
|
||||||
enableWsh = *connSettings.WshEnabled
|
enableWsh = *connSettings.ConnWshEnabled
|
||||||
}
|
}
|
||||||
if connSettings.AskBeforeWshInstall != nil {
|
if connSettings.ConnAskBeforeWshInstall != nil {
|
||||||
askBeforeInstall = *connSettings.AskBeforeWshInstall
|
askBeforeInstall = *connSettings.ConnAskBeforeWshInstall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if enableWsh {
|
if enableWsh {
|
||||||
@ -661,6 +693,9 @@ func GetConnectionsList() ([]string, error) {
|
|||||||
hasConnected = append(hasConnected, stat.Connection)
|
hasConnected = append(hasConnected, stat.Connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fromInternal := GetConnectionsFromInternalConfig()
|
||||||
|
|
||||||
fromConfig, err := GetConnectionsFromConfig()
|
fromConfig, err := GetConnectionsFromConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -670,7 +705,7 @@ func GetConnectionsList() ([]string, error) {
|
|||||||
alreadyUsed := make(map[string]struct{})
|
alreadyUsed := make(map[string]struct{})
|
||||||
var connList []string
|
var connList []string
|
||||||
|
|
||||||
for _, subList := range [][]string{currentlyRunning, hasConnected, fromConfig} {
|
for _, subList := range [][]string{currentlyRunning, hasConnected, fromInternal, fromConfig} {
|
||||||
for _, pattern := range subList {
|
for _, pattern := range subList {
|
||||||
if _, used := alreadyUsed[pattern]; !used {
|
if _, used := alreadyUsed[pattern]; !used {
|
||||||
connList = append(connList, pattern)
|
connList = append(connList, pattern)
|
||||||
@ -682,6 +717,15 @@ func GetConnectionsList() ([]string, error) {
|
|||||||
return connList, nil
|
return connList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetConnectionsFromInternalConfig() []string {
|
||||||
|
var internalNames []string
|
||||||
|
config := wconfig.ReadFullConfig()
|
||||||
|
for internalName := range config.Connections {
|
||||||
|
internalNames = append(internalNames, internalName)
|
||||||
|
}
|
||||||
|
return internalNames
|
||||||
|
}
|
||||||
|
|
||||||
func GetConnectionsFromConfig() ([]string, error) {
|
func GetConnectionsFromConfig() ([]string, error) {
|
||||||
home := wavebase.GetHomeDir()
|
home := wavebase.GetHomeDir()
|
||||||
localConfig := filepath.Join(home, ".ssh", "config")
|
localConfig := filepath.Join(home, ".ssh", "config")
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/wavetermdev/waveterm/pkg/userinput"
|
"github.com/wavetermdev/waveterm/pkg/userinput"
|
||||||
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wconfig"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/crypto/ssh/agent"
|
"golang.org/x/crypto/ssh/agent"
|
||||||
@ -649,7 +650,13 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
|||||||
connFlags.SshHostName = opts.SSHHost
|
connFlags.SshHostName = opts.SSHHost
|
||||||
connFlags.SshPort = fmt.Sprintf("%d", opts.SSHPort)
|
connFlags.SshPort = fmt.Sprintf("%d", opts.SSHPort)
|
||||||
|
|
||||||
sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords)
|
rawName := opts.String()
|
||||||
|
savedKeywords, ok := wconfig.ReadFullConfig().Connections[rawName]
|
||||||
|
if !ok {
|
||||||
|
savedKeywords = wshrpc.ConnKeywords{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords, &savedKeywords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err}
|
||||||
}
|
}
|
||||||
@ -685,7 +692,7 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.
|
|||||||
return client, debugInfo.JumpNum, nil
|
return client, debugInfo.JumpNum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) {
|
func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords, savedKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) {
|
||||||
sshKeywords := &wshrpc.ConnKeywords{}
|
sshKeywords := &wshrpc.ConnKeywords{}
|
||||||
|
|
||||||
if userProvidedOpts.SshUser != "" {
|
if userProvidedOpts.SshUser != "" {
|
||||||
@ -716,7 +723,13 @@ func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *w
|
|||||||
sshKeywords.SshPort = "22"
|
sshKeywords.SshPort = "22"
|
||||||
}
|
}
|
||||||
|
|
||||||
sshKeywords.SshIdentityFile = append(userProvidedOpts.SshIdentityFile, configKeywords.SshIdentityFile...)
|
// use internal config ones
|
||||||
|
if savedKeywords != nil {
|
||||||
|
sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, savedKeywords.SshIdentityFile...)
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, userProvidedOpts.SshIdentityFile...)
|
||||||
|
sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, configKeywords.SshIdentityFile...)
|
||||||
|
|
||||||
// these are not officially supported in the waveterm frontend but can be configured
|
// these are not officially supported in the waveterm frontend but can be configured
|
||||||
// in ssh config files
|
// in ssh config files
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"askbeforewshinstall": true
|
|
||||||
}
|
|
@ -452,8 +452,16 @@ type CommandRemoteWriteFileData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ConnKeywords struct {
|
type ConnKeywords struct {
|
||||||
WshEnabled *bool `json:"wshenabled,omitempty"`
|
ConnWshEnabled *bool `json:"conn:wshenabled,omitempty"`
|
||||||
AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"`
|
ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"`
|
||||||
|
|
||||||
|
DisplayHidden *bool `json:"display:hidden,omitempty"`
|
||||||
|
DisplayOrder float32 `json:"display:order,omitempty"`
|
||||||
|
|
||||||
|
TermClear bool `json:"term:*,omitempty"`
|
||||||
|
TermFontSize float64 `json:"term:fontsize,omitempty"`
|
||||||
|
TermFontFamily string `json:"term:fontfamily,omitempty"`
|
||||||
|
TermTheme string `json:"term:theme,omitempty"`
|
||||||
|
|
||||||
SshUser string `json:"ssh:user,omitempty"`
|
SshUser string `json:"ssh:user,omitempty"`
|
||||||
SshHostName string `json:"ssh:hostname,omitempty"`
|
SshHostName string `json:"ssh:hostname,omitempty"`
|
||||||
|
Loading…
Reference in New Issue
Block a user