imcp 0.0.1
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/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
- package/.github/acl/access.yml +20 -0
- package/.github/compliance/inventory.yml +5 -0
- package/.github/policies/jit.yml +19 -0
- package/README.md +137 -0
- package/dist/cli/commands/install.d.ts +2 -0
- package/dist/cli/commands/install.js +105 -0
- package/dist/cli/commands/list.d.ts +2 -0
- package/dist/cli/commands/list.js +90 -0
- package/dist/cli/commands/pull.d.ts +2 -0
- package/dist/cli/commands/pull.js +17 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +32 -0
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.js +32 -0
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.js +17 -0
- package/dist/cli/commands/uninstall.d.ts +2 -0
- package/dist/cli/commands/uninstall.js +39 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +114 -0
- package/dist/core/ConfigurationProvider.d.ts +31 -0
- package/dist/core/ConfigurationProvider.js +416 -0
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +144 -0
- package/dist/core/MCPManager.d.ts +17 -0
- package/dist/core/MCPManager.js +98 -0
- package/dist/core/RequirementService.d.ts +45 -0
- package/dist/core/RequirementService.js +123 -0
- package/dist/core/constants.d.ts +29 -0
- package/dist/core/constants.js +55 -0
- package/dist/core/installers/BaseInstaller.d.ts +73 -0
- package/dist/core/installers/BaseInstaller.js +247 -0
- package/dist/core/installers/ClientInstaller.d.ts +17 -0
- package/dist/core/installers/ClientInstaller.js +307 -0
- package/dist/core/installers/CommandInstaller.d.ts +36 -0
- package/dist/core/installers/CommandInstaller.js +170 -0
- package/dist/core/installers/GeneralInstaller.d.ts +32 -0
- package/dist/core/installers/GeneralInstaller.js +87 -0
- package/dist/core/installers/InstallerFactory.d.ts +52 -0
- package/dist/core/installers/InstallerFactory.js +95 -0
- package/dist/core/installers/NpmInstaller.d.ts +25 -0
- package/dist/core/installers/NpmInstaller.js +123 -0
- package/dist/core/installers/PipInstaller.d.ts +25 -0
- package/dist/core/installers/PipInstaller.js +114 -0
- package/dist/core/installers/RequirementInstaller.d.ts +32 -0
- package/dist/core/installers/RequirementInstaller.js +3 -0
- package/dist/core/installers/index.d.ts +6 -0
- package/dist/core/installers/index.js +7 -0
- package/dist/core/types.d.ts +152 -0
- package/dist/core/types.js +16 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +19 -0
- package/dist/services/InstallRequestValidator.d.ts +21 -0
- package/dist/services/InstallRequestValidator.js +99 -0
- package/dist/services/ServerService.d.ts +47 -0
- package/dist/services/ServerService.js +145 -0
- package/dist/utils/UpdateCheckTracker.d.ts +39 -0
- package/dist/utils/UpdateCheckTracker.js +80 -0
- package/dist/utils/clientUtils.d.ts +29 -0
- package/dist/utils/clientUtils.js +105 -0
- package/dist/utils/feedUtils.d.ts +5 -0
- package/dist/utils/feedUtils.js +29 -0
- package/dist/utils/githubAuth.d.ts +1 -0
- package/dist/utils/githubAuth.js +123 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +90 -0
- package/dist/utils/osUtils.d.ts +16 -0
- package/dist/utils/osUtils.js +235 -0
- package/dist/web/public/css/modal.css +250 -0
- package/dist/web/public/css/notifications.css +70 -0
- package/dist/web/public/index.html +157 -0
- package/dist/web/public/js/api.js +213 -0
- package/dist/web/public/js/modal.js +572 -0
- package/dist/web/public/js/notifications.js +99 -0
- package/dist/web/public/js/serverCategoryDetails.js +210 -0
- package/dist/web/public/js/serverCategoryList.js +82 -0
- package/dist/web/public/modal.html +61 -0
- package/dist/web/public/styles.css +155 -0
- package/dist/web/server.d.ts +5 -0
- package/dist/web/server.js +150 -0
- package/package.json +53 -0
- package/src/cli/commands/install.ts +140 -0
- package/src/cli/commands/list.ts +112 -0
- package/src/cli/commands/pull.ts +16 -0
- package/src/cli/commands/serve.ts +37 -0
- package/src/cli/commands/uninstall.ts +54 -0
- package/src/cli/index.ts +127 -0
- package/src/core/ConfigurationProvider.ts +489 -0
- package/src/core/InstallationService.ts +173 -0
- package/src/core/MCPManager.ts +134 -0
- package/src/core/RequirementService.ts +147 -0
- package/src/core/constants.ts +61 -0
- package/src/core/installers/BaseInstaller.ts +292 -0
- package/src/core/installers/ClientInstaller.ts +423 -0
- package/src/core/installers/CommandInstaller.ts +185 -0
- package/src/core/installers/GeneralInstaller.ts +89 -0
- package/src/core/installers/InstallerFactory.ts +109 -0
- package/src/core/installers/NpmInstaller.ts +128 -0
- package/src/core/installers/PipInstaller.ts +121 -0
- package/src/core/installers/RequirementInstaller.ts +38 -0
- package/src/core/installers/index.ts +9 -0
- package/src/core/types.ts +163 -0
- package/src/index.ts +44 -0
- package/src/services/InstallRequestValidator.ts +112 -0
- package/src/services/ServerService.ts +181 -0
- package/src/utils/UpdateCheckTracker.ts +86 -0
- package/src/utils/clientUtils.ts +112 -0
- package/src/utils/feedUtils.ts +31 -0
- package/src/utils/githubAuth.ts +142 -0
- package/src/utils/logger.ts +101 -0
- package/src/utils/osUtils.ts +250 -0
- package/src/web/public/css/modal.css +250 -0
- package/src/web/public/css/notifications.css +70 -0
- package/src/web/public/index.html +157 -0
- package/src/web/public/js/api.js +213 -0
- package/src/web/public/js/modal.js +572 -0
- package/src/web/public/js/notifications.js +99 -0
- package/src/web/public/js/serverCategoryDetails.js +210 -0
- package/src/web/public/js/serverCategoryList.js +82 -0
- package/src/web/public/modal.html +61 -0
- package/src/web/public/styles.css +155 -0
- package/src/web/server.ts +195 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SETTINGS_DIR } from '../core/constants.js';
|
|
4
|
+
|
|
5
|
+
export class Logger {
|
|
6
|
+
private static verbose = false;
|
|
7
|
+
private static fileLoggingEnabled = true;
|
|
8
|
+
private static logsDir = path.join(SETTINGS_DIR, 'logs');
|
|
9
|
+
|
|
10
|
+
static setVerbose(isVerbose: boolean): void {
|
|
11
|
+
this.verbose = isVerbose;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static setFileLogging(enabled: boolean): void {
|
|
15
|
+
this.fileLoggingEnabled = enabled;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private static async ensureLogsDirExists(): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
await fs.promises.mkdir(this.logsDir, { recursive: true });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error(`Failed to create logs directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
|
+
// Disable file logging if directory creation fails
|
|
24
|
+
this.fileLoggingEnabled = false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static getLogFileName(): string {
|
|
29
|
+
const now = new Date();
|
|
30
|
+
const year = now.getFullYear();
|
|
31
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
32
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
33
|
+
return `${year}-${month}-${day}.log`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private static getTimestamp(): string {
|
|
37
|
+
return new Date().toISOString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static async writeToLogFile(level: string, content: string): Promise<void> {
|
|
41
|
+
if (!this.fileLoggingEnabled) return;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await this.ensureLogsDirExists();
|
|
45
|
+
const logFile = path.join(this.logsDir, this.getLogFileName());
|
|
46
|
+
const logEntry = `[${this.getTimestamp()}] [${level}] ${content}\n`;
|
|
47
|
+
|
|
48
|
+
await fs.promises.appendFile(logFile, logEntry, { encoding: 'utf8' });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`Failed to write to log file: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static async log(message: string): Promise<void> {
|
|
55
|
+
console.log(message);
|
|
56
|
+
await this.writeToLogFile('INFO', message);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static async debug(message: string | object): Promise<void> {
|
|
60
|
+
let formattedMessage: string;
|
|
61
|
+
|
|
62
|
+
if (typeof message === 'object') {
|
|
63
|
+
formattedMessage = JSON.stringify(message, null, 2);
|
|
64
|
+
if (this.verbose) {
|
|
65
|
+
console.log(formattedMessage);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
formattedMessage = message;
|
|
69
|
+
if (this.verbose) {
|
|
70
|
+
console.log(message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.verbose) {
|
|
75
|
+
await this.writeToLogFile('DEBUG', formattedMessage);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static async error(message: string, error?: unknown): Promise<void> {
|
|
80
|
+
console.error(message);
|
|
81
|
+
|
|
82
|
+
let logMessage = message;
|
|
83
|
+
|
|
84
|
+
if (this.verbose && error) {
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
const errorDetails = {
|
|
87
|
+
name: error.name,
|
|
88
|
+
message: error.message,
|
|
89
|
+
stack: error.stack
|
|
90
|
+
};
|
|
91
|
+
console.error('Error details:', errorDetails);
|
|
92
|
+
logMessage += `\nError details: ${JSON.stringify(errorDetails, null, 2)}`;
|
|
93
|
+
} else {
|
|
94
|
+
console.error('Error details:', error);
|
|
95
|
+
logMessage += `\nError details: ${JSON.stringify(error, null, 2)}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await this.writeToLogFile('ERROR', logMessage);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { OSType } from '../core/types.js';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import util from 'util';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const execAsync = util.promisify(exec);
|
|
10
|
+
|
|
11
|
+
export function getOSType(): OSType {
|
|
12
|
+
const platform = os.platform();
|
|
13
|
+
Logger.debug({
|
|
14
|
+
action: 'get_os_type',
|
|
15
|
+
platform
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
switch (platform) {
|
|
19
|
+
case 'win32':
|
|
20
|
+
return OSType.Windows;
|
|
21
|
+
case 'darwin':
|
|
22
|
+
return OSType.MacOS;
|
|
23
|
+
case 'linux':
|
|
24
|
+
return OSType.Linux;
|
|
25
|
+
default:
|
|
26
|
+
const error = `Unsupported operating system: ${platform}`;
|
|
27
|
+
Logger.error(error);
|
|
28
|
+
throw new Error(error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function installCLI(tool: 'gh' | 'git'): Promise<void> {
|
|
33
|
+
const osType = getOSType();
|
|
34
|
+
Logger.debug({
|
|
35
|
+
action: 'install_cli',
|
|
36
|
+
tool,
|
|
37
|
+
osType
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
switch (osType) {
|
|
42
|
+
case OSType.Windows:
|
|
43
|
+
if (tool === 'git') {
|
|
44
|
+
await execAsync('winget install --id Git.Git -e --source winget --silent');
|
|
45
|
+
} else {
|
|
46
|
+
await execAsync('winget install --id GitHub.cli --silent');
|
|
47
|
+
}
|
|
48
|
+
// Refresh PATH environment variable after installation
|
|
49
|
+
await refreshPathEnv();
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case OSType.MacOS:
|
|
53
|
+
if (tool === 'git') {
|
|
54
|
+
await execAsync('brew install git');
|
|
55
|
+
} else {
|
|
56
|
+
await execAsync('brew install gh');
|
|
57
|
+
}
|
|
58
|
+
// On macOS, we may need to source profile files
|
|
59
|
+
if (tool === 'git') {
|
|
60
|
+
await execAsync('source ~/.zshrc || source ~/.bash_profile || source ~/.bashrc || true');
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case OSType.Linux:
|
|
65
|
+
if (tool === 'git') {
|
|
66
|
+
await execAsync('sudo apt-get update && sudo apt-get install -y git');
|
|
67
|
+
} else {
|
|
68
|
+
await execAsync('curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null && sudo apt update && sudo apt install -y gh');
|
|
69
|
+
}
|
|
70
|
+
// Source bash profile to refresh PATH
|
|
71
|
+
await execAsync('source ~/.bashrc || source ~/.profile || true');
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
throw new Error(`Unsupported operating system for installing ${tool}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Wait a moment for system to register the new binaries
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
80
|
+
|
|
81
|
+
Logger.debug({
|
|
82
|
+
action: 'install_cli_success',
|
|
83
|
+
tool,
|
|
84
|
+
osType
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
Logger.error('Failed to install CLI tool', {
|
|
88
|
+
tool,
|
|
89
|
+
osType,
|
|
90
|
+
error
|
|
91
|
+
});
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Refreshes the PATH environment variable for the current Node.js process
|
|
98
|
+
* This is necessary because when tools are installed using package managers,
|
|
99
|
+
* they update the system PATH but the current Node process doesn't see those changes.
|
|
100
|
+
*/
|
|
101
|
+
export async function refreshPathEnv(): Promise<void> {
|
|
102
|
+
const osType = getOSType();
|
|
103
|
+
Logger.debug({
|
|
104
|
+
action: 'refresh_path_env',
|
|
105
|
+
osType
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
switch (osType) {
|
|
110
|
+
case OSType.Windows:
|
|
111
|
+
// On Windows, get the PATH from registry
|
|
112
|
+
const { stdout: winPath } = await execAsync('powershell -command "[Environment]::GetEnvironmentVariable(\'Path\', \'Machine\') + \';\' + [Environment]::GetEnvironmentVariable(\'Path\', \'User\')"');
|
|
113
|
+
if (winPath) {
|
|
114
|
+
process.env.PATH = winPath.trim();
|
|
115
|
+
Logger.debug('Refreshed PATH from Windows registry');
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case OSType.MacOS:
|
|
120
|
+
case OSType.Linux:
|
|
121
|
+
// On Unix systems, typical installation locations if PATH isn't updated
|
|
122
|
+
const commonPaths = [
|
|
123
|
+
'/usr/local/bin',
|
|
124
|
+
'/usr/bin',
|
|
125
|
+
'/bin',
|
|
126
|
+
'/usr/sbin',
|
|
127
|
+
'/sbin',
|
|
128
|
+
'/usr/local/git/bin',
|
|
129
|
+
'/opt/homebrew/bin', // For M1 Macs
|
|
130
|
+
`${os.homedir()}/.local/bin`
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
// Ensure these paths are in process.env.PATH
|
|
134
|
+
if (process.env.PATH) {
|
|
135
|
+
const currentPaths = process.env.PATH.split(':');
|
|
136
|
+
const newPaths = [...new Set([...currentPaths, ...commonPaths])];
|
|
137
|
+
process.env.PATH = newPaths.join(':');
|
|
138
|
+
Logger.debug('Expanded PATH with common Unix binary locations');
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Logger.debug({
|
|
144
|
+
action: 'refresh_path_env_success',
|
|
145
|
+
path: process.env.PATH
|
|
146
|
+
});
|
|
147
|
+
} catch (error) {
|
|
148
|
+
Logger.error('Failed to refresh PATH environment variable', error);
|
|
149
|
+
// Continue execution even if PATH refresh fails
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function isToolInstalled(tool: 'gh' | 'git', retries = 3): Promise<boolean> {
|
|
154
|
+
try {
|
|
155
|
+
Logger.debug({
|
|
156
|
+
action: 'check_tool_installed',
|
|
157
|
+
tool,
|
|
158
|
+
retries
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Try to execute tool command
|
|
162
|
+
try {
|
|
163
|
+
await execAsync(`${tool} --version`);
|
|
164
|
+
Logger.debug({
|
|
165
|
+
action: 'check_tool_installed_success',
|
|
166
|
+
tool,
|
|
167
|
+
installed: true
|
|
168
|
+
});
|
|
169
|
+
return true;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
// If we have retries left, refresh PATH and try again
|
|
172
|
+
if (retries > 0) {
|
|
173
|
+
Logger.debug(`${tool} not found, refreshing PATH and retrying...`);
|
|
174
|
+
|
|
175
|
+
// Refresh environment PATH variable
|
|
176
|
+
await refreshPathEnv();
|
|
177
|
+
|
|
178
|
+
// Wait a moment before retrying
|
|
179
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
180
|
+
|
|
181
|
+
// Recursive retry with decremented counter
|
|
182
|
+
return isToolInstalled(tool, retries - 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// No retries left, tool is not installed
|
|
186
|
+
Logger.debug({
|
|
187
|
+
action: 'check_tool_installed_success',
|
|
188
|
+
tool,
|
|
189
|
+
installed: false
|
|
190
|
+
});
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
Logger.error(`Error checking if ${tool} is installed`, error);
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Opens a URL in the default browser
|
|
201
|
+
* @param url The URL to open
|
|
202
|
+
* @returns A promise that resolves when the browser is launched
|
|
203
|
+
*/
|
|
204
|
+
export async function openBrowser(url: string): Promise<void> {
|
|
205
|
+
const osType = getOSType();
|
|
206
|
+
Logger.debug({
|
|
207
|
+
action: 'open_browser',
|
|
208
|
+
url,
|
|
209
|
+
osType
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
switch (osType) {
|
|
214
|
+
case OSType.Windows:
|
|
215
|
+
await execAsync(`start ${url}`);
|
|
216
|
+
break;
|
|
217
|
+
case OSType.MacOS:
|
|
218
|
+
await execAsync(`open ${url}`);
|
|
219
|
+
break;
|
|
220
|
+
case OSType.Linux:
|
|
221
|
+
// Try different commands that might be available
|
|
222
|
+
try {
|
|
223
|
+
await execAsync(`xdg-open ${url}`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// Try alternative commands
|
|
226
|
+
try {
|
|
227
|
+
await execAsync(`sensible-browser ${url}`);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
await execAsync(`gnome-open ${url}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
default:
|
|
234
|
+
throw new Error(`Unsupported operating system for opening browser`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Logger.debug({
|
|
238
|
+
action: 'open_browser_success',
|
|
239
|
+
url,
|
|
240
|
+
osType
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
Logger.error('Failed to open browser', {
|
|
244
|
+
url,
|
|
245
|
+
osType,
|
|
246
|
+
error
|
|
247
|
+
});
|
|
248
|
+
// Don't throw the error - just log it and continue
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/* Import Inter font */
|
|
2
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
3
|
+
|
|
4
|
+
/* Base styles */
|
|
5
|
+
body {
|
|
6
|
+
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Modal container */
|
|
10
|
+
.modal {
|
|
11
|
+
display: none;
|
|
12
|
+
position: fixed;
|
|
13
|
+
z-index: 50;
|
|
14
|
+
left: 0;
|
|
15
|
+
top: 0;
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
overflow: auto;
|
|
19
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
20
|
+
backdrop-filter: blur(4px);
|
|
21
|
+
animation: fadeIn 0.2s ease-out;
|
|
22
|
+
/* Debug: Ensure loading modal is always visible and content is not hidden */
|
|
23
|
+
#installLoadingModal {
|
|
24
|
+
z-index: 3000 !important;
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
27
|
+
#installLoadingModal .modal-content {
|
|
28
|
+
z-index: 3100 !important;
|
|
29
|
+
pointer-events: auto;
|
|
30
|
+
background: #fff !important;
|
|
31
|
+
border: 3px solid #3498db !important;
|
|
32
|
+
color: #222 !important;
|
|
33
|
+
min-width: 320px;
|
|
34
|
+
min-height: 120px;
|
|
35
|
+
opacity: 1 !important;
|
|
36
|
+
box-shadow: 0 0 16px #3498db;
|
|
37
|
+
}
|
|
38
|
+
/* Loading modal always on top */
|
|
39
|
+
#installLoadingModal {
|
|
40
|
+
z-index: 2000 !important;
|
|
41
|
+
display: none;
|
|
42
|
+
}
|
|
43
|
+
#installLoadingModal .modal-content {
|
|
44
|
+
z-index: 2100 !important;
|
|
45
|
+
pointer-events: auto;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Modal content */
|
|
50
|
+
.modal-content {
|
|
51
|
+
background-color: #ffffff;
|
|
52
|
+
margin: 10% auto;
|
|
53
|
+
padding: 2rem;
|
|
54
|
+
border: none;
|
|
55
|
+
border-radius: 1rem;
|
|
56
|
+
width: 90%;
|
|
57
|
+
max-width: 550px;
|
|
58
|
+
position: relative;
|
|
59
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
60
|
+
transform: translateY(0);
|
|
61
|
+
transition: all 0.3s ease-out;
|
|
62
|
+
animation: slideIn 0.3s ease-out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Close button */
|
|
66
|
+
.close {
|
|
67
|
+
position: absolute;
|
|
68
|
+
right: 1.5rem;
|
|
69
|
+
top: 1.5rem;
|
|
70
|
+
font-size: 1.5rem;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
color: #6b7280;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
width: 32px;
|
|
75
|
+
height: 32px;
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
border-radius: 50%;
|
|
80
|
+
transition: color 0.2s ease;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.close:hover {
|
|
84
|
+
background-color: #f3f4f6;
|
|
85
|
+
color: #111827;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Sections layout */
|
|
89
|
+
.modal-sections {
|
|
90
|
+
margin-top: 1.5rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.section-container {
|
|
94
|
+
padding: 1.25rem;
|
|
95
|
+
margin-bottom: 1.5rem;
|
|
96
|
+
border: 1px solid #e5e7eb;
|
|
97
|
+
border-radius: 0.75rem;
|
|
98
|
+
background-color: #f9fafb;
|
|
99
|
+
transition: all 0.2s ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.section-title {
|
|
103
|
+
margin-bottom: 1rem;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Client grid */
|
|
107
|
+
.client-grid {
|
|
108
|
+
display: grid;
|
|
109
|
+
grid-template-columns: 1fr;
|
|
110
|
+
gap: 0.75rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Client item styling */
|
|
114
|
+
.client-item {
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: space-between;
|
|
118
|
+
padding: 0.75rem 1rem;
|
|
119
|
+
border-radius: 0.5rem;
|
|
120
|
+
border: 2px solid #e5e7eb;
|
|
121
|
+
background-color: #ffffff;
|
|
122
|
+
transition: all 0.2s ease;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
user-select: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.client-item:hover {
|
|
128
|
+
background-color: #f3f4f6;
|
|
129
|
+
transform: translateY(-1px);
|
|
130
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.client-item.selected {
|
|
134
|
+
border-color: #2563eb;
|
|
135
|
+
background-color: #eff6ff;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Client item info section */
|
|
139
|
+
.client-info {
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
gap: 0.75rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Status badges */
|
|
146
|
+
.status-badge {
|
|
147
|
+
display: inline-flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
padding: 0.25rem 0.75rem;
|
|
150
|
+
border-radius: 9999px;
|
|
151
|
+
font-size: 0.75rem;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.status-badge.installed {
|
|
156
|
+
background-color: #dcfce7;
|
|
157
|
+
color: #166534;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.status-badge.not-installed {
|
|
161
|
+
background-color: #fee2e2;
|
|
162
|
+
color: #991b1b;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.status-badge.pending {
|
|
166
|
+
background-color: #fef3c7;
|
|
167
|
+
color: #92400e;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Non-selectable items (installed or in-progress) */
|
|
171
|
+
.client-item.non-selectable {
|
|
172
|
+
background-color: #f9fafb;
|
|
173
|
+
cursor: default;
|
|
174
|
+
opacity: 0.85;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.client-item.non-selectable:hover {
|
|
178
|
+
transform: none;
|
|
179
|
+
box-shadow: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* Specific styling for installed items */
|
|
183
|
+
.client-item.installed-item {
|
|
184
|
+
background-color: #f0fdf4;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Specific styling for in-progress items */
|
|
188
|
+
.client-item.in-progress-item {
|
|
189
|
+
background-color: #fef3c7;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Environment variables section */
|
|
193
|
+
#modalEnvInputs input {
|
|
194
|
+
width: 100%;
|
|
195
|
+
padding: 0.75rem 1rem;
|
|
196
|
+
border: 1px solid #e5e7eb;
|
|
197
|
+
border-radius: 0.5rem;
|
|
198
|
+
transition: all 0.2s ease;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#modalEnvInputs input:focus {
|
|
202
|
+
outline: none;
|
|
203
|
+
border-color: #2563eb;
|
|
204
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Form buttons */
|
|
208
|
+
.submit-button {
|
|
209
|
+
background-color: #2563eb;
|
|
210
|
+
color: white;
|
|
211
|
+
padding: 0.625rem 1.25rem;
|
|
212
|
+
border-radius: 0.5rem;
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
transition: all 0.2s ease;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.submit-button:hover {
|
|
218
|
+
background-color: #1d4ed8;
|
|
219
|
+
transform: translateY(-1px);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.submit-button:active {
|
|
223
|
+
transform: translateY(0);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Animations */
|
|
227
|
+
@keyframes fadeIn {
|
|
228
|
+
from { opacity: 0; }
|
|
229
|
+
to { opacity: 1; }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@keyframes slideIn {
|
|
233
|
+
from {
|
|
234
|
+
opacity: 0;
|
|
235
|
+
transform: translateY(-20px);
|
|
236
|
+
}
|
|
237
|
+
to {
|
|
238
|
+
opacity: 1;
|
|
239
|
+
transform: translateY(0);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Center loading icon in loading modal */
|
|
244
|
+
#installLoadingModal .loading-icon {
|
|
245
|
+
display: flex;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
align-items: center;
|
|
248
|
+
width: 100%;
|
|
249
|
+
margin-bottom: 8px;
|
|
250
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Alert container */
|
|
2
|
+
.alert-container {
|
|
3
|
+
z-index: 999999; /* Extremely high to appear above modals */
|
|
4
|
+
position: fixed;
|
|
5
|
+
top: 0 !important;
|
|
6
|
+
left: 0 !important;
|
|
7
|
+
right: 0 !important;
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 10px;
|
|
12
|
+
pointer-events: none; /* Allow clicking through the container */
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.alert {
|
|
16
|
+
min-width: 350px;
|
|
17
|
+
max-width: 500px;
|
|
18
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
19
|
+
backdrop-filter: blur(8px);
|
|
20
|
+
border-width: 2px;
|
|
21
|
+
pointer-events: auto; /* Re-enable pointer events for alerts */
|
|
22
|
+
padding: 0.75rem 1.5rem;
|
|
23
|
+
font-size: 1.1rem;
|
|
24
|
+
font-weight: 500;
|
|
25
|
+
margin-top: 10px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.alert:first-child {
|
|
29
|
+
margin-top: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.alert.alert-success {
|
|
33
|
+
background-color: rgba(25, 135, 84, 0.98);
|
|
34
|
+
border-color: #146c43;
|
|
35
|
+
color: white;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.alert.alert-error {
|
|
40
|
+
background-color: rgba(220, 53, 69, 0.98);
|
|
41
|
+
border-color: #b02a37;
|
|
42
|
+
color: white;
|
|
43
|
+
font-weight: 500;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.alert .btn-close {
|
|
47
|
+
filter: brightness(0) invert(1);
|
|
48
|
+
opacity: 0.8;
|
|
49
|
+
padding: 0.75rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* When modal is open, add backdrop to alerts */
|
|
53
|
+
.modal.show ~ .alert-container .alert {
|
|
54
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
55
|
+
backdrop-filter: blur(12px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Modal customization */
|
|
59
|
+
.modal-content {
|
|
60
|
+
border: none;
|
|
61
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.modal-header {
|
|
65
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.modal-footer {
|
|
69
|
+
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
|
70
|
+
}
|