Skip to content

Commit 08390fc

Browse files
committed
frontend/project/datastore: make sshfs port number configurable
1 parent f5f0ddf commit 08390fc

File tree

3 files changed

+59
-14
lines changed

3 files changed

+59
-14
lines changed

src/packages/database/postgres/project-queries.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
2+
* This file is part of CoCalc: Copyright © 2020 – 2025 Sagemath, Inc.
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

@@ -14,7 +14,7 @@ import { DatastoreConfig } from "@cocalc/util/types";
1414

1515
export async function project_has_network_access(
1616
db: PostgreSQL,
17-
project_id: string
17+
project_id: string,
1818
): Promise<boolean> {
1919
let x;
2020
try {
@@ -52,7 +52,7 @@ interface GetDSOpts {
5252
}
5353

5454
async function get_datastore(
55-
opts: GetDSOpts
55+
opts: GetDSOpts,
5656
): Promise<{ [key: string]: DatastoreConfig }> {
5757
const { db, account_id, project_id } = opts;
5858
const q: { users: any; addons?: any } = await query({
@@ -73,21 +73,28 @@ export async function project_datastore_set(
7373
db: PostgreSQL,
7474
account_id: string,
7575
project_id: string,
76-
config: any
76+
config: any,
7777
): Promise<void> {
7878
// L("project_datastore_set", config);
7979

8080
if (config.name == null) throw Error("configuration 'name' is not defined");
8181
if (typeof config.type !== "string")
8282
throw Error(
83-
"configuration 'type' is not defined (must be 'gcs', 'sshfs', ...)"
83+
"configuration 'type' is not defined (must be 'gcs', 'sshfs', ...)",
8484
);
8585

8686
// check data from user
8787
for (const [key, val] of Object.entries(config)) {
88-
if (typeof val !== "string" && typeof val !== "boolean") {
88+
if (
89+
typeof val !== "string" &&
90+
typeof val !== "boolean" &&
91+
typeof val !== "number"
92+
) {
8993
throw new Error(`Invalid value -- '${key}' is not a valid type`);
9094
}
95+
if (typeof val === "number" && (val < 0 || val > 2 ** 32)) {
96+
throw new Error(`Invalid value -- '${key}' is out of range`);
97+
}
9198
if (typeof val === "string" && val.length > 100000) {
9299
throw new Error(`Invalid value -- '${key}' is too long`);
93100
}
@@ -128,7 +135,7 @@ export async function project_datastore_del(
128135
db: PostgreSQL,
129136
account_id: string,
130137
project_id: string,
131-
name: string
138+
name: string,
132139
): Promise<void> {
133140
L("project_datastore_del", name);
134141
if (typeof name !== "string" || name.length == 0) {
@@ -149,7 +156,7 @@ export async function project_datastore_del(
149156
export async function project_datastore_get(
150157
db: PostgreSQL,
151158
account_id: string,
152-
project_id: string
159+
project_id: string,
153160
): Promise<any> {
154161
try {
155162
const ds = await get_datastore({

src/packages/frontend/project/settings/datastore.tsx

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* This file is part of CoCalc: Copyright © 2021 – 2023 Sagemath, Inc.
2+
* This file is part of CoCalc: Copyright © 2021 – 2025 Sagemath, Inc.
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

@@ -21,6 +21,7 @@ import {
2121
Checkbox,
2222
Form,
2323
Input,
24+
InputNumber,
2425
Modal,
2526
Popconfirm,
2627
Space,
@@ -78,6 +79,18 @@ const RULE_ALPHANUM = [
7879
},
7980
];
8081

82+
const RULE_PORT = [
83+
{
84+
validator: (_: any, value: number | null) => {
85+
if (value == null) return Promise.resolve();
86+
if (!Number.isInteger(value) || value < 1) {
87+
return Promise.reject("Port must be an integer greater or equal to 1.");
88+
}
89+
return Promise.resolve();
90+
},
91+
},
92+
];
93+
8194
// convert the configuration from the DB to fields for the table
8295
function raw2configs(raw: { [name: string]: Config }): Config[] {
8396
const ret: Config[] = [];
@@ -96,11 +109,15 @@ function raw2configs(raw: { [name: string]: Config }): Config[] {
96109
v.about = `Bucket: ${v.bucket}`;
97110
break;
98111
case "sshfs":
99-
v.about = [
112+
const about_sshfs = [
100113
`User: ${v.user}`,
101114
`Host: ${v.host}`,
102115
`Path: ${v.path ?? `/user/${v.user}`}`,
103-
].join("\n");
116+
];
117+
if (v.port != null && v.port !== 22) {
118+
about_sshfs.push(`Port: ${v.port}`);
119+
}
120+
v.about = about_sshfs.join("\n");
104121
break;
105122
default:
106123
unreachable(v);
@@ -175,6 +192,7 @@ export const Datastore: React.FC<Props> = React.memo((props: Props) => {
175192
user: "",
176193
host: "",
177194
path: "",
195+
port: 22,
178196
});
179197
break;
180198
default:
@@ -308,6 +326,9 @@ export const Datastore: React.FC<Props> = React.memo((props: Props) => {
308326
const conf: Config = { ...record };
309327
conf.secret = "";
310328
delete conf.about;
329+
if (conf.type === "sshfs" && conf.port == null) {
330+
conf.port = 22;
331+
}
311332
set_new_config(conf);
312333
set_form_readonly(conf.readonly ?? READONLY_DEFAULT);
313334
setEditMode(true);
@@ -424,7 +445,7 @@ export const Datastore: React.FC<Props> = React.memo((props: Props) => {
424445
>
425446
<div style={{ fontSize: "90%" }}>
426447
<Icon
427-
name={record.readonly ?? false ? "lock" : "lock-open"}
448+
name={(record.readonly ?? false) ? "lock" : "lock-open"}
428449
/>{" "}
429450
{record.readonly ? "Read-only" : "Read/write"}
430451
</div>
@@ -588,9 +609,16 @@ export const Datastore: React.FC<Props> = React.memo((props: Props) => {
588609
}
589610

590611
async function save_config(values: any): Promise<void> {
591-
values.readonly = form_readonly;
612+
const config = { ...values, readonly: form_readonly };
613+
if ("port" in config) {
614+
if (config.port == null || config.port === "") {
615+
delete config.port;
616+
} else if (config.port === 22) {
617+
delete config.port;
618+
}
619+
}
592620
try {
593-
await set(values);
621+
await set(config);
594622
} catch (err) {
595623
if (err) set_error(err);
596624
}
@@ -774,6 +802,15 @@ function NewSSHFS({
774802
>
775803
<Input placeholder="login.server.edu" />
776804
</Form.Item>
805+
<Form.Item
806+
label="Port"
807+
name="port"
808+
rules={RULE_PORT}
809+
tooltip="The SSH port, defaults to 22"
810+
help="Leave empty to use port 22."
811+
>
812+
<InputNumber min={1} placeholder="22" style={{ width: "100%" }} />
813+
</Form.Item>
777814
<Form.Item
778815
label="Remote Path"
779816
name="path"

src/packages/util/types/datastore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface ConfigSSHFS extends ConfigCommon {
2626
user: string;
2727
host: string;
2828
path?: string; // remote path, defaults to /home/user
29+
port?: number;
2930
}
3031

3132
export type DatastoreConfig = ConfigS3 | ConfigGCS | ConfigSSHFS;

0 commit comments

Comments
 (0)