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.
Files changed (124) hide show
  1. package/.github/ISSUE_TEMPLATE/JitAccess.yml +28 -0
  2. package/.github/acl/access.yml +20 -0
  3. package/.github/compliance/inventory.yml +5 -0
  4. package/.github/policies/jit.yml +19 -0
  5. package/README.md +137 -0
  6. package/dist/cli/commands/install.d.ts +2 -0
  7. package/dist/cli/commands/install.js +105 -0
  8. package/dist/cli/commands/list.d.ts +2 -0
  9. package/dist/cli/commands/list.js +90 -0
  10. package/dist/cli/commands/pull.d.ts +2 -0
  11. package/dist/cli/commands/pull.js +17 -0
  12. package/dist/cli/commands/serve.d.ts +2 -0
  13. package/dist/cli/commands/serve.js +32 -0
  14. package/dist/cli/commands/start.d.ts +2 -0
  15. package/dist/cli/commands/start.js +32 -0
  16. package/dist/cli/commands/sync.d.ts +2 -0
  17. package/dist/cli/commands/sync.js +17 -0
  18. package/dist/cli/commands/uninstall.d.ts +2 -0
  19. package/dist/cli/commands/uninstall.js +39 -0
  20. package/dist/cli/index.d.ts +2 -0
  21. package/dist/cli/index.js +114 -0
  22. package/dist/core/ConfigurationProvider.d.ts +31 -0
  23. package/dist/core/ConfigurationProvider.js +416 -0
  24. package/dist/core/InstallationService.d.ts +17 -0
  25. package/dist/core/InstallationService.js +144 -0
  26. package/dist/core/MCPManager.d.ts +17 -0
  27. package/dist/core/MCPManager.js +98 -0
  28. package/dist/core/RequirementService.d.ts +45 -0
  29. package/dist/core/RequirementService.js +123 -0
  30. package/dist/core/constants.d.ts +29 -0
  31. package/dist/core/constants.js +55 -0
  32. package/dist/core/installers/BaseInstaller.d.ts +73 -0
  33. package/dist/core/installers/BaseInstaller.js +247 -0
  34. package/dist/core/installers/ClientInstaller.d.ts +17 -0
  35. package/dist/core/installers/ClientInstaller.js +307 -0
  36. package/dist/core/installers/CommandInstaller.d.ts +36 -0
  37. package/dist/core/installers/CommandInstaller.js +170 -0
  38. package/dist/core/installers/GeneralInstaller.d.ts +32 -0
  39. package/dist/core/installers/GeneralInstaller.js +87 -0
  40. package/dist/core/installers/InstallerFactory.d.ts +52 -0
  41. package/dist/core/installers/InstallerFactory.js +95 -0
  42. package/dist/core/installers/NpmInstaller.d.ts +25 -0
  43. package/dist/core/installers/NpmInstaller.js +123 -0
  44. package/dist/core/installers/PipInstaller.d.ts +25 -0
  45. package/dist/core/installers/PipInstaller.js +114 -0
  46. package/dist/core/installers/RequirementInstaller.d.ts +32 -0
  47. package/dist/core/installers/RequirementInstaller.js +3 -0
  48. package/dist/core/installers/index.d.ts +6 -0
  49. package/dist/core/installers/index.js +7 -0
  50. package/dist/core/types.d.ts +152 -0
  51. package/dist/core/types.js +16 -0
  52. package/dist/index.d.ts +11 -0
  53. package/dist/index.js +19 -0
  54. package/dist/services/InstallRequestValidator.d.ts +21 -0
  55. package/dist/services/InstallRequestValidator.js +99 -0
  56. package/dist/services/ServerService.d.ts +47 -0
  57. package/dist/services/ServerService.js +145 -0
  58. package/dist/utils/UpdateCheckTracker.d.ts +39 -0
  59. package/dist/utils/UpdateCheckTracker.js +80 -0
  60. package/dist/utils/clientUtils.d.ts +29 -0
  61. package/dist/utils/clientUtils.js +105 -0
  62. package/dist/utils/feedUtils.d.ts +5 -0
  63. package/dist/utils/feedUtils.js +29 -0
  64. package/dist/utils/githubAuth.d.ts +1 -0
  65. package/dist/utils/githubAuth.js +123 -0
  66. package/dist/utils/logger.d.ts +14 -0
  67. package/dist/utils/logger.js +90 -0
  68. package/dist/utils/osUtils.d.ts +16 -0
  69. package/dist/utils/osUtils.js +235 -0
  70. package/dist/web/public/css/modal.css +250 -0
  71. package/dist/web/public/css/notifications.css +70 -0
  72. package/dist/web/public/index.html +157 -0
  73. package/dist/web/public/js/api.js +213 -0
  74. package/dist/web/public/js/modal.js +572 -0
  75. package/dist/web/public/js/notifications.js +99 -0
  76. package/dist/web/public/js/serverCategoryDetails.js +210 -0
  77. package/dist/web/public/js/serverCategoryList.js +82 -0
  78. package/dist/web/public/modal.html +61 -0
  79. package/dist/web/public/styles.css +155 -0
  80. package/dist/web/server.d.ts +5 -0
  81. package/dist/web/server.js +150 -0
  82. package/package.json +53 -0
  83. package/src/cli/commands/install.ts +140 -0
  84. package/src/cli/commands/list.ts +112 -0
  85. package/src/cli/commands/pull.ts +16 -0
  86. package/src/cli/commands/serve.ts +37 -0
  87. package/src/cli/commands/uninstall.ts +54 -0
  88. package/src/cli/index.ts +127 -0
  89. package/src/core/ConfigurationProvider.ts +489 -0
  90. package/src/core/InstallationService.ts +173 -0
  91. package/src/core/MCPManager.ts +134 -0
  92. package/src/core/RequirementService.ts +147 -0
  93. package/src/core/constants.ts +61 -0
  94. package/src/core/installers/BaseInstaller.ts +292 -0
  95. package/src/core/installers/ClientInstaller.ts +423 -0
  96. package/src/core/installers/CommandInstaller.ts +185 -0
  97. package/src/core/installers/GeneralInstaller.ts +89 -0
  98. package/src/core/installers/InstallerFactory.ts +109 -0
  99. package/src/core/installers/NpmInstaller.ts +128 -0
  100. package/src/core/installers/PipInstaller.ts +121 -0
  101. package/src/core/installers/RequirementInstaller.ts +38 -0
  102. package/src/core/installers/index.ts +9 -0
  103. package/src/core/types.ts +163 -0
  104. package/src/index.ts +44 -0
  105. package/src/services/InstallRequestValidator.ts +112 -0
  106. package/src/services/ServerService.ts +181 -0
  107. package/src/utils/UpdateCheckTracker.ts +86 -0
  108. package/src/utils/clientUtils.ts +112 -0
  109. package/src/utils/feedUtils.ts +31 -0
  110. package/src/utils/githubAuth.ts +142 -0
  111. package/src/utils/logger.ts +101 -0
  112. package/src/utils/osUtils.ts +250 -0
  113. package/src/web/public/css/modal.css +250 -0
  114. package/src/web/public/css/notifications.css +70 -0
  115. package/src/web/public/index.html +157 -0
  116. package/src/web/public/js/api.js +213 -0
  117. package/src/web/public/js/modal.js +572 -0
  118. package/src/web/public/js/notifications.js +99 -0
  119. package/src/web/public/js/serverCategoryDetails.js +210 -0
  120. package/src/web/public/js/serverCategoryList.js +82 -0
  121. package/src/web/public/modal.html +61 -0
  122. package/src/web/public/styles.css +155 -0
  123. package/src/web/server.ts +195 -0
  124. 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
+ }