fnos-cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +70 -268
- package/bin/fnos +5 -1
- package/docs/commands.md +775 -0
- package/package.json +1 -1
- package/src/commands/auth.js +42 -55
- package/src/commands/catalog/applications.js +30 -0
- package/src/commands/catalog/identity-security.js +52 -0
- package/src/commands/catalog/index.js +21 -0
- package/src/commands/catalog/monitoring.js +40 -0
- package/src/commands/catalog/network-sharing.js +81 -0
- package/src/commands/catalog/storage.js +107 -0
- package/src/commands/catalog/system.js +51 -0
- package/src/commands/index.js +18 -139
- package/src/commands/options.js +128 -0
- package/src/commands/runner.js +37 -0
- package/src/index.js +41 -51
- package/src/utils/auth-helper.js +13 -6
- package/src/utils/client.js +135 -113
- package/src/utils/errors.js +11 -0
- package/src/utils/logger.js +25 -1
- package/src/utils/settings.js +33 -72
- package/src/constants.js +0 -69
package/src/utils/client.js
CHANGED
|
@@ -1,141 +1,163 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FnosClient wrapper with authentication
|
|
2
|
+
* FnosClient wrapper with fnos 0.4 authentication.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const sdk = require('fnos');
|
|
6
6
|
const settings = require('./settings');
|
|
7
7
|
const { logger } = require('./logger');
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const { credentials, endpoint, username, password, timeout = 60, saveCredentials = false } = options;
|
|
22
|
-
|
|
23
|
-
// Use provided credentials object
|
|
24
|
-
if (credentials) {
|
|
25
|
-
return await createClientWithCredentials(credentials, timeout, saveCredentials);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Legacy support: build credentials from individual parameters
|
|
29
|
-
const savedCredentials = settings.getCredentials();
|
|
30
|
-
const finalEndpoint = endpoint || savedCredentials?.endpoint;
|
|
31
|
-
const finalUsername = username || savedCredentials?.username;
|
|
32
|
-
const finalPassword = password || savedCredentials?.password;
|
|
33
|
-
|
|
34
|
-
if (!finalEndpoint || !finalUsername) {
|
|
35
|
-
throw new Error('Missing credentials. Please run "fnos login" first or provide -e, -u, -p parameters.');
|
|
9
|
+
const SDK_CLASS_NAMES = [
|
|
10
|
+
'ResourceMonitor', 'Store', 'SystemInfo', 'User', 'Network', 'File', 'SAC',
|
|
11
|
+
'DockerManager', 'EventLogger', 'Share', 'Notify', 'IscsiManager',
|
|
12
|
+
'BackupManager', 'DownloadCenter', 'IPBlocker', 'LicenseManager',
|
|
13
|
+
'LiveUpdate', 'MountManager', 'NetworkServer', 'Security', 'SystemRestore'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
class AuthenticationError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'AuthenticationError';
|
|
20
|
+
this.exitCode = 3;
|
|
36
21
|
}
|
|
37
|
-
|
|
38
|
-
return await createClientWithCredentials(
|
|
39
|
-
{
|
|
40
|
-
endpoint: finalEndpoint,
|
|
41
|
-
username: finalUsername,
|
|
42
|
-
password: finalPassword,
|
|
43
|
-
token: savedCredentials?.token,
|
|
44
|
-
longToken: savedCredentials?.longToken,
|
|
45
|
-
secret: savedCredentials?.secret
|
|
46
|
-
},
|
|
47
|
-
timeout,
|
|
48
|
-
saveCredentials
|
|
49
|
-
);
|
|
50
22
|
}
|
|
51
23
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* @param {number} timeout - Connection timeout in seconds
|
|
59
|
-
* @param {boolean} saveToSettings - Whether to save credentials to settings
|
|
60
|
-
* @returns {Promise<FnosClient>} Authenticated client
|
|
61
|
-
*/
|
|
62
|
-
async function createClientWithCredentials(credentials, timeout, saveToSettings) {
|
|
63
|
-
const { endpoint, username, password } = credentials;
|
|
24
|
+
function canUseToken(credentials) {
|
|
25
|
+
return credentials.credentialVersion === 2 &&
|
|
26
|
+
credentials.secretFormat === 'decrypted-v1' &&
|
|
27
|
+
[credentials.token, credentials.longToken, credentials.secret]
|
|
28
|
+
.every((value) => typeof value === 'string' && value.length > 0);
|
|
29
|
+
}
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
31
|
+
function tokenLoginSucceeded(result) {
|
|
32
|
+
return Boolean(result && (result.result === 'succ' || result.errno === 0));
|
|
33
|
+
}
|
|
68
34
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
35
|
+
// fnos 0.4.0 treats every response containing data.hostName as the connection
|
|
36
|
+
// handshake, so a later SystemInfo.getHostName response never reaches its waiter.
|
|
37
|
+
function installHostNameResponseCompatibility(client) {
|
|
38
|
+
if (typeof client.processMessage !== 'function' || !(client.pendingRequests instanceof Map)) return;
|
|
39
|
+
const processMessage = client.processMessage.bind(client);
|
|
40
|
+
client.processMessage = (message) => {
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = JSON.parse(message);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return processMessage(message);
|
|
46
|
+
}
|
|
47
|
+
const hasHostName = response && response.data &&
|
|
48
|
+
typeof response.data === 'object' && 'hostName' in response.data;
|
|
49
|
+
const pending = hasHostName && typeof response.reqid === 'string'
|
|
50
|
+
? client.pendingRequests.get(response.reqid)
|
|
51
|
+
: null;
|
|
52
|
+
if (pending) {
|
|
53
|
+
if (pending.timeoutTimer) clearTimeout(pending.timeoutTimer);
|
|
54
|
+
client.pendingRequests.delete(response.reqid);
|
|
55
|
+
pending.future.resolve(response);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
return processMessage(message);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
72
61
|
|
|
73
|
-
|
|
74
|
-
if (!password)
|
|
75
|
-
|
|
62
|
+
async function passwordLogin(client, credentials, options) {
|
|
63
|
+
if (!credentials.password) throw new AuthenticationError('Password is required.');
|
|
64
|
+
let result;
|
|
65
|
+
try {
|
|
66
|
+
result = await client.login(credentials.username, credentials.password, 10000);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (client.loginResponse?.result === 'fail') {
|
|
69
|
+
throw new AuthenticationError(error.message || 'Login failed.');
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
76
72
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
logger.info('Logged in successfully');
|
|
81
|
-
|
|
82
|
-
// Save credentials only if explicitly requested (e.g., from login command)
|
|
83
|
-
if (saveToSettings) {
|
|
84
|
-
settings.saveCredentials({
|
|
85
|
-
endpoint,
|
|
86
|
-
username,
|
|
87
|
-
password,
|
|
88
|
-
token: loginResult.token,
|
|
89
|
-
longToken: loginResult.longToken,
|
|
90
|
-
secret: loginResult.secret
|
|
91
|
-
});
|
|
92
|
-
logger.info('Credentials saved');
|
|
73
|
+
if (!result) throw new AuthenticationError('Login failed.');
|
|
74
|
+
if (result.twofaSetupRequired) {
|
|
75
|
+
throw new AuthenticationError('This account must bind two-factor authentication in the fnOS web interface first.');
|
|
93
76
|
}
|
|
94
|
-
|
|
95
|
-
|
|
77
|
+
if (result.twofaRequired) {
|
|
78
|
+
if (!/^\d{6}$/.test(options.twofaCode || '')) {
|
|
79
|
+
throw new AuthenticationError('A six-digit --twofa-code is required for this account.');
|
|
80
|
+
}
|
|
81
|
+
result = await client.submitTwofaCode(options.twofaCode, options.trustDevice === true, 10000);
|
|
82
|
+
}
|
|
83
|
+
if (!result || result.result !== 'succ' || !result.token || !result.secret) {
|
|
84
|
+
throw new AuthenticationError(result?.msg || result?.errmsg || 'Login failed.');
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
96
87
|
}
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
89
|
+
async function createClientWithCredentials(credentials, options = {}, dependencies = {}) {
|
|
90
|
+
const sdkModule = dependencies.sdk || sdk;
|
|
91
|
+
const settingsStore = dependencies.settings || settings;
|
|
92
|
+
const activeLogger = dependencies.logger || logger;
|
|
93
|
+
const Client = sdkModule.FnosClient;
|
|
94
|
+
const client = new Client();
|
|
95
|
+
const timeout = options.timeout ?? 60;
|
|
96
|
+
|
|
105
97
|
try {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
activeLogger.info(`Connecting to ${credentials.endpoint}...`);
|
|
99
|
+
await client.connect(credentials.endpoint, timeout * 1000, false, options.verifySsl === true ? false : true);
|
|
100
|
+
installHostNameResponseCompatibility(client);
|
|
101
|
+
let result;
|
|
102
|
+
let usedToken = false;
|
|
103
|
+
|
|
104
|
+
if (canUseToken(credentials)) {
|
|
105
|
+
result = await client.loginViaToken(credentials.token, credentials.longToken, credentials.secret, 10000);
|
|
106
|
+
usedToken = tokenLoginSucceeded(result);
|
|
107
|
+
if (!usedToken) activeLogger.debug('Saved token was rejected; falling back to password login.');
|
|
111
108
|
}
|
|
109
|
+
|
|
110
|
+
if (!usedToken) {
|
|
111
|
+
result = await passwordLogin(client, credentials, options);
|
|
112
|
+
if (options.saveCredentials || credentials.source === 'settings_file') {
|
|
113
|
+
const decryptedSecret = client.getDecryptedSecret();
|
|
114
|
+
if (typeof decryptedSecret !== 'string' || decryptedSecret.length === 0) {
|
|
115
|
+
throw new AuthenticationError('Login secret decryption failed.');
|
|
116
|
+
}
|
|
117
|
+
settingsStore.saveCredentials({
|
|
118
|
+
endpoint: credentials.endpoint,
|
|
119
|
+
username: credentials.username,
|
|
120
|
+
password: credentials.password,
|
|
121
|
+
credentialVersion: 2,
|
|
122
|
+
token: result.token || null,
|
|
123
|
+
longToken: result.longToken || null,
|
|
124
|
+
secret: decryptedSecret,
|
|
125
|
+
secretFormat: 'decrypted-v1'
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return client;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
client.close();
|
|
112
133
|
throw error;
|
|
113
134
|
}
|
|
114
135
|
}
|
|
115
136
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
function getSDKInstance(client, className) {
|
|
123
|
-
const instances = {
|
|
124
|
-
ResourceMonitor: new ResourceMonitor(client),
|
|
125
|
-
Store: new Store(client),
|
|
126
|
-
SystemInfo: new SystemInfo(client),
|
|
127
|
-
User: new User(client),
|
|
128
|
-
Network: new Network(client),
|
|
129
|
-
File: new File(client),
|
|
130
|
-
SAC: new SAC(client)
|
|
137
|
+
async function createClient(options = {}, dependencies = {}) {
|
|
138
|
+
const credentials = options.credentials || {
|
|
139
|
+
endpoint: options.endpoint,
|
|
140
|
+
username: options.username,
|
|
141
|
+
password: options.password,
|
|
142
|
+
source: 'command_line'
|
|
131
143
|
};
|
|
144
|
+
if (!credentials.endpoint || !credentials.username) {
|
|
145
|
+
throw new AuthenticationError('Missing credentials. Run "fnos login" or provide -e/-u/-p.');
|
|
146
|
+
}
|
|
147
|
+
return createClientWithCredentials(credentials, options, dependencies);
|
|
148
|
+
}
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
if (!
|
|
150
|
+
function getSDKInstance(client, className, sdkModule = sdk) {
|
|
151
|
+
if (!SDK_CLASS_NAMES.includes(className) || typeof sdkModule[className] !== 'function') {
|
|
135
152
|
throw new Error(`Unknown SDK class: ${className}`);
|
|
136
153
|
}
|
|
137
|
-
|
|
138
|
-
return instance;
|
|
154
|
+
return new sdkModule[className](client);
|
|
139
155
|
}
|
|
140
156
|
|
|
141
|
-
module.exports = {
|
|
157
|
+
module.exports = {
|
|
158
|
+
AuthenticationError,
|
|
159
|
+
SDK_CLASS_NAMES,
|
|
160
|
+
createClient,
|
|
161
|
+
createClientWithCredentials,
|
|
162
|
+
getSDKInstance
|
|
163
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { HTTPSRequiredError } = require('fnos');
|
|
2
|
+
const { redactSensitive } = require('./logger');
|
|
3
|
+
|
|
4
|
+
function errorMessage(error) {
|
|
5
|
+
const message = error instanceof HTTPSRequiredError
|
|
6
|
+
? `fnOS requires HTTPS: ${error.requestedUri} was redirected to ${error.redirectUri}. Use the corresponding wss:// endpoint.`
|
|
7
|
+
: error?.message || String(error);
|
|
8
|
+
return redactSensitive(message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { errorMessage };
|
package/src/utils/logger.js
CHANGED
|
@@ -19,6 +19,26 @@ if (!fs.existsSync(logsDir)) {
|
|
|
19
19
|
const randomNum = crypto.randomBytes(4).toString('hex');
|
|
20
20
|
const logFilename = `fnos-${new Date().toISOString().split('T')[0]}-${randomNum}.log`;
|
|
21
21
|
|
|
22
|
+
const SENSITIVE_VALUE = /((?:"?(?:longToken|long_token|accessToken|access_token|twofaCode|twofa_code|password|secret|token|code|hmac)"?)\s*[:=]\s*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\[REDACTED\]|[^\s,}\]]+)/gi;
|
|
23
|
+
const SIGNED_MESSAGE = /^(Calculated iz-result:|Sending msg to channel:|已发送请求:)\s*.*$/i;
|
|
24
|
+
|
|
25
|
+
function redactSensitive(value) {
|
|
26
|
+
const message = String(value);
|
|
27
|
+
if (SIGNED_MESSAGE.test(message)) return message.replace(SIGNED_MESSAGE, '$1 [REDACTED]');
|
|
28
|
+
return message
|
|
29
|
+
.replace(SENSITIVE_VALUE, (match, prefix, rawValue) => {
|
|
30
|
+
const quote = rawValue[0] === '"' || rawValue[0] === "'" ? rawValue[0] : '';
|
|
31
|
+
const value = quote ? rawValue.slice(1, -1) : rawValue;
|
|
32
|
+
return value === '[REDACTED]' ? match : `${prefix}${quote}[REDACTED]${quote}`;
|
|
33
|
+
})
|
|
34
|
+
.replace(/(服务器返回的secret:\s*)\S+/gi, '$1[REDACTED]');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const redactFormat = winston.format((info) => {
|
|
38
|
+
info.message = redactSensitive(info.message);
|
|
39
|
+
return info;
|
|
40
|
+
});
|
|
41
|
+
|
|
22
42
|
// Log format
|
|
23
43
|
const logFormat = winston.format.printf(({ timestamp, level, message }) => {
|
|
24
44
|
return `${timestamp} - ${level.toUpperCase()} - ${message}`;
|
|
@@ -52,6 +72,7 @@ class StderrTransport extends winston.Transport {
|
|
|
52
72
|
const logger = winston.createLogger({
|
|
53
73
|
level: 'silly', // Capture all levels for file output
|
|
54
74
|
format: winston.format.combine(
|
|
75
|
+
redactFormat(),
|
|
55
76
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
56
77
|
logFormat
|
|
57
78
|
),
|
|
@@ -60,6 +81,7 @@ const logger = winston.createLogger({
|
|
|
60
81
|
new StderrTransport({
|
|
61
82
|
level: 'error', // Default: only show errors
|
|
62
83
|
format: winston.format.combine(
|
|
84
|
+
redactFormat(),
|
|
63
85
|
winston.format.colorize(),
|
|
64
86
|
logFormat
|
|
65
87
|
)
|
|
@@ -102,10 +124,12 @@ function setLogLevel(verbose) {
|
|
|
102
124
|
if (sdkConsoleTransport) {
|
|
103
125
|
fnosLogger.default.remove(sdkConsoleTransport);
|
|
104
126
|
}
|
|
127
|
+
fnosLogger.default.level = consoleTransport.level;
|
|
105
128
|
// Add our custom stderr transport
|
|
106
129
|
const sdkStderrTransport = new StderrTransport({
|
|
107
130
|
level: consoleTransport.level,
|
|
108
131
|
format: winston.format.combine(
|
|
132
|
+
redactFormat(),
|
|
109
133
|
winston.format.colorize(),
|
|
110
134
|
logFormat
|
|
111
135
|
)
|
|
@@ -117,4 +141,4 @@ function setLogLevel(verbose) {
|
|
|
117
141
|
}
|
|
118
142
|
}
|
|
119
143
|
|
|
120
|
-
module.exports = { logger, setLogLevel };
|
|
144
|
+
module.exports = { logger, redactSensitive, setLogLevel };
|
package/src/utils/settings.js
CHANGED
|
@@ -2,122 +2,83 @@
|
|
|
2
2
|
* Settings manager for fnos
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const
|
|
7
|
-
const
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const path = require('node:path');
|
|
8
8
|
|
|
9
9
|
class Settings {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.settingsDir = path.join(
|
|
10
|
+
constructor(homeDir = os.homedir()) {
|
|
11
|
+
this.settingsDir = path.join(homeDir, '.fnos');
|
|
12
12
|
this.settingsPath = path.join(this.settingsDir, 'settings.json');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* Ensure settings directory exists
|
|
17
|
-
*/
|
|
18
15
|
_ensureDir() {
|
|
19
|
-
|
|
20
|
-
fs.mkdirSync(this.settingsDir, { recursive: true });
|
|
21
|
-
}
|
|
16
|
+
fs.mkdirSync(this.settingsDir, { recursive: true, mode: 0o700 });
|
|
22
17
|
}
|
|
23
18
|
|
|
24
|
-
/**
|
|
25
|
-
* Load settings from file
|
|
26
|
-
* @returns {Object|null} Settings object or null if not exists
|
|
27
|
-
*/
|
|
28
19
|
load() {
|
|
29
|
-
if (!fs.existsSync(this.settingsPath))
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
20
|
+
if (!fs.existsSync(this.settingsPath)) return null;
|
|
32
21
|
try {
|
|
33
|
-
|
|
34
|
-
return JSON.parse(data);
|
|
22
|
+
return JSON.parse(fs.readFileSync(this.settingsPath, 'utf8'));
|
|
35
23
|
} catch (error) {
|
|
36
|
-
console.error(
|
|
24
|
+
console.error(error instanceof SyntaxError
|
|
25
|
+
? 'Failed to load settings: invalid JSON.'
|
|
26
|
+
: 'Failed to load settings.');
|
|
37
27
|
return null;
|
|
38
28
|
}
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
/**
|
|
42
|
-
* Save settings to file
|
|
43
|
-
* @param {Object} data - Settings data to save
|
|
44
|
-
*/
|
|
45
31
|
save(data) {
|
|
46
32
|
this._ensureDir();
|
|
47
33
|
try {
|
|
48
|
-
fs.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2), {
|
|
49
|
-
|
|
50
|
-
});
|
|
34
|
+
fs.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
35
|
+
fs.chmodSync(this.settingsPath, 0o600);
|
|
51
36
|
} catch (error) {
|
|
52
|
-
console.error('Failed to save settings
|
|
37
|
+
console.error('Failed to save settings.');
|
|
53
38
|
throw error;
|
|
54
39
|
}
|
|
55
40
|
}
|
|
56
41
|
|
|
57
|
-
/**
|
|
58
|
-
* Clear settings file
|
|
59
|
-
*/
|
|
60
42
|
clear() {
|
|
61
|
-
if (fs.existsSync(this.settingsPath))
|
|
62
|
-
fs.unlinkSync(this.settingsPath);
|
|
63
|
-
}
|
|
43
|
+
if (fs.existsSync(this.settingsPath)) fs.unlinkSync(this.settingsPath);
|
|
64
44
|
}
|
|
65
45
|
|
|
66
|
-
/**
|
|
67
|
-
* Clear only authentication credentials, preserving other settings
|
|
68
|
-
*/
|
|
69
46
|
clearCredentials() {
|
|
70
47
|
const existingSettings = this.load();
|
|
71
|
-
if (!existingSettings)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
authFields.forEach(field => delete existingSettings[field]);
|
|
78
|
-
|
|
79
|
-
// Always save the file (even if empty), to preserve the settings.json file
|
|
48
|
+
if (!existingSettings) return;
|
|
49
|
+
const authFields = [
|
|
50
|
+
'endpoint', 'username', 'password', 'token', 'longToken', 'secret',
|
|
51
|
+
'credentialVersion', 'secretFormat'
|
|
52
|
+
];
|
|
53
|
+
authFields.forEach((field) => delete existingSettings[field]);
|
|
80
54
|
this.save(existingSettings);
|
|
81
55
|
}
|
|
82
56
|
|
|
83
|
-
/**
|
|
84
|
-
* Check if settings file exists
|
|
85
|
-
* @returns {boolean} True if settings file exists
|
|
86
|
-
*/
|
|
87
57
|
exists() {
|
|
88
58
|
return fs.existsSync(this.settingsPath);
|
|
89
59
|
}
|
|
90
60
|
|
|
91
|
-
/**
|
|
92
|
-
* Get authentication credentials
|
|
93
|
-
* @returns {Object|null} Credentials object or null
|
|
94
|
-
*/
|
|
95
61
|
getCredentials() {
|
|
96
|
-
const
|
|
97
|
-
if (!
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
password: settings.password,
|
|
104
|
-
token: settings.token,
|
|
105
|
-
longToken: settings.longToken,
|
|
106
|
-
secret: settings.secret
|
|
107
|
-
};
|
|
62
|
+
const data = this.load();
|
|
63
|
+
if (!data) return null;
|
|
64
|
+
const fields = [
|
|
65
|
+
'endpoint', 'username', 'password', 'token', 'longToken', 'secret',
|
|
66
|
+
'credentialVersion', 'secretFormat'
|
|
67
|
+
];
|
|
68
|
+
return Object.fromEntries(fields.map((field) => [field, data[field]]));
|
|
108
69
|
}
|
|
109
70
|
|
|
110
|
-
/**
|
|
111
|
-
* Save authentication credentials
|
|
112
|
-
* @param {Object} credentials - Credentials to save
|
|
113
|
-
*/
|
|
114
71
|
saveCredentials(credentials) {
|
|
115
72
|
const existingSettings = this.load() || {};
|
|
116
73
|
this.save({
|
|
117
74
|
...existingSettings,
|
|
75
|
+
credentialVersion: 2,
|
|
76
|
+
secretFormat: 'decrypted-v1',
|
|
118
77
|
...credentials
|
|
119
78
|
});
|
|
120
79
|
}
|
|
121
80
|
}
|
|
122
81
|
|
|
123
|
-
|
|
82
|
+
const settings = new Settings();
|
|
83
|
+
module.exports = settings;
|
|
84
|
+
module.exports.Settings = Settings;
|
package/src/constants.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command mapping configuration
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const COMMAND_MAPPING = {
|
|
6
|
-
resmon: {
|
|
7
|
-
className: 'ResourceMonitor',
|
|
8
|
-
commands: {
|
|
9
|
-
cpu: { method: 'cpu', description: 'Get CPU resource monitoring information' },
|
|
10
|
-
gpu: { method: 'gpu', description: 'Get GPU resource monitoring information' },
|
|
11
|
-
mem: { method: 'memory', description: 'Get memory resource monitoring information' },
|
|
12
|
-
disk: { method: 'disk', description: 'Get disk resource monitoring information' },
|
|
13
|
-
net: { method: 'net', description: 'Get network resource monitoring information' },
|
|
14
|
-
gen: { method: 'general', description: 'Get general resource monitoring information', params: ['items'] }
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
store: {
|
|
18
|
-
className: 'Store',
|
|
19
|
-
commands: {
|
|
20
|
-
general: { method: 'general', description: 'Get storage general information' },
|
|
21
|
-
calcSpace: { method: 'calculateSpace', description: 'Calculate storage space information' },
|
|
22
|
-
listDisk: { method: 'listDisks', description: 'List disk information', params: ['noHotSpare'] },
|
|
23
|
-
diskSmart: { method: 'getDiskSmart', description: 'Get disk SMART information', params: ['disk'] },
|
|
24
|
-
state: { method: 'getState', description: 'Get storage state information', params: ['name', 'uuid'] }
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
sysinfo: {
|
|
28
|
-
className: 'SystemInfo',
|
|
29
|
-
commands: {
|
|
30
|
-
getHostName: { method: 'getHostName', description: 'Get host name information' },
|
|
31
|
-
getTrimVersion: { method: 'getTrimVersion', description: 'Get Trim version information' },
|
|
32
|
-
getMachineId: { method: 'getMachineId', description: 'Get machine ID information' },
|
|
33
|
-
getHardwareInfo: { method: 'getHardwareInfo', description: 'Get hardware information' },
|
|
34
|
-
getUptime: { method: 'getUptime', description: 'Get system uptime information' }
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
user: {
|
|
38
|
-
className: 'User',
|
|
39
|
-
commands: {
|
|
40
|
-
info: { method: 'getInfo', description: 'Get user information' },
|
|
41
|
-
listUG: { method: 'listUserGroups', description: 'List users and groups' },
|
|
42
|
-
groupUsers: { method: 'groupUsers', description: 'Get user grouping information' },
|
|
43
|
-
isAdmin: { method: 'isAdmin', description: 'Check if current user is admin' }
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
network: {
|
|
47
|
-
className: 'Network',
|
|
48
|
-
commands: {
|
|
49
|
-
list: { method: 'list', description: 'List network information', params: ['type'] },
|
|
50
|
-
detect: { method: 'detect', description: 'Detect network interface', params: ['ifName'] }
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
file: {
|
|
54
|
-
className: 'File',
|
|
55
|
-
commands: {
|
|
56
|
-
ls: { method: 'list', description: 'List files and directories', params: ['path'] },
|
|
57
|
-
mkdir: { method: 'mkdir', description: 'Create directory', params: ['path'] },
|
|
58
|
-
rm: { method: 'remove', description: 'Remove files or directories', params: ['files', 'moveToTrashbin', 'details'] }
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
sac: {
|
|
62
|
-
className: 'SAC',
|
|
63
|
-
commands: {
|
|
64
|
-
upsStatus: { method: 'upsStatus', description: 'Get UPS status information' }
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
module.exports = { COMMAND_MAPPING };
|