mindroot 7.7.0__py3-none-any.whl → 8.1.0__py3-none-any.whl
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.
Potentially problematic release.
This version of mindroot might be problematic. Click here for more details.
- mindroot/coreplugins/admin/router.py +3 -1
- mindroot/coreplugins/env_manager/__init__.py +3 -0
- mindroot/coreplugins/env_manager/inject/admin.jinja2 +16 -0
- mindroot/coreplugins/env_manager/mod.py +228 -0
- mindroot/coreplugins/env_manager/router.py +40 -0
- mindroot/coreplugins/env_manager/static/css/env-manager.css +263 -0
- mindroot/coreplugins/env_manager/static/js/env-manager.js +380 -0
- mindroot/server.py +12 -25
- mindroot-8.1.0.dist-info/METADATA +15 -0
- {mindroot-7.7.0.dist-info → mindroot-8.1.0.dist-info}/RECORD +14 -8
- {mindroot-7.7.0.dist-info → mindroot-8.1.0.dist-info}/WHEEL +1 -1
- mindroot-7.7.0.dist-info/METADATA +0 -310
- {mindroot-7.7.0.dist-info → mindroot-8.1.0.dist-info}/entry_points.txt +0 -0
- {mindroot-7.7.0.dist-info → mindroot-8.1.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-7.7.0.dist-info → mindroot-8.1.0.dist-info}/top_level.txt +0 -0
|
@@ -42,4 +42,6 @@ router.include_router(agent_router)
|
|
|
42
42
|
from .server_router import router as server_router
|
|
43
43
|
router.include_router(server_router, prefix="/admin/server", tags=["server"])
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
# Import and include the env_manager router
|
|
46
|
+
from coreplugins.env_manager.router import router as env_manager_router
|
|
47
|
+
router.include_router(env_manager_router)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% block head %}
|
|
2
|
+
<link rel="stylesheet" href="/env_manager/static/css/env-manager.css">
|
|
3
|
+
<script type="module" src="/env_manager/static/js/env-manager.js"></script>
|
|
4
|
+
{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<details>
|
|
8
|
+
<summary>
|
|
9
|
+
<span class="material-icons">settings_applications</span>
|
|
10
|
+
<span>Environment Variables</span>
|
|
11
|
+
</summary>
|
|
12
|
+
<div class="details-content">
|
|
13
|
+
<env-manager theme="dark" scope="local"></env-manager>
|
|
14
|
+
</div>
|
|
15
|
+
</details>
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from lib.providers.services import service
|
|
7
|
+
from lib.plugins import list_enabled, get_plugin_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def should_skip_directory(directory):
|
|
11
|
+
"""Check if a directory should be skipped during scanning.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
directory (str): Path to check
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: True if the directory should be skipped, False otherwise
|
|
18
|
+
"""
|
|
19
|
+
# Directories to skip
|
|
20
|
+
skip_dirs = [
|
|
21
|
+
'__pycache__',
|
|
22
|
+
'node_modules',
|
|
23
|
+
'static/js',
|
|
24
|
+
'static/css',
|
|
25
|
+
'venv',
|
|
26
|
+
'env',
|
|
27
|
+
'.env',
|
|
28
|
+
'virtualenv',
|
|
29
|
+
'site-packages',
|
|
30
|
+
'dist-packages',
|
|
31
|
+
'.git',
|
|
32
|
+
'.idea',
|
|
33
|
+
'.vscode',
|
|
34
|
+
'build',
|
|
35
|
+
'dist',
|
|
36
|
+
'egg-info'
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
# Check if any part of the path contains a directory to skip
|
|
40
|
+
path_parts = Path(directory).parts
|
|
41
|
+
for skip_dir in skip_dirs:
|
|
42
|
+
if skip_dir in path_parts:
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def scan_directory_for_env_vars(directory):
|
|
49
|
+
"""Scan a directory for environment variable references using grep.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
directory (str): Path to the directory to scan
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
set: Set of environment variable names found
|
|
56
|
+
"""
|
|
57
|
+
env_vars = set()
|
|
58
|
+
|
|
59
|
+
# Skip if this is a directory we should ignore
|
|
60
|
+
if should_skip_directory(directory):
|
|
61
|
+
return env_vars
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Use grep to find os.environ references - much faster than parsing each file
|
|
65
|
+
cmd = [
|
|
66
|
+
'grep', '-r',
|
|
67
|
+
'-E', "(os\.environ\[\"|'|os\.environ\.get\(\"|')",
|
|
68
|
+
'--include=*.py',
|
|
69
|
+
'--exclude-dir=venv',
|
|
70
|
+
'--exclude-dir=env',
|
|
71
|
+
'--exclude-dir=.env',
|
|
72
|
+
'--exclude-dir=site-packages',
|
|
73
|
+
'--exclude-dir=dist-packages',
|
|
74
|
+
'--exclude-dir=__pycache__',
|
|
75
|
+
'--exclude-dir=node_modules',
|
|
76
|
+
directory
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
80
|
+
|
|
81
|
+
if result.returncode not in [0, 1]: # 0 = matches found, 1 = no matches
|
|
82
|
+
print(f"Error running grep on {directory}: {result.stderr}")
|
|
83
|
+
return env_vars
|
|
84
|
+
|
|
85
|
+
# Extract variable names using regex
|
|
86
|
+
pattern1 = r"os\.environ\.get\(['\"]([A-Za-z0-9_]+)['\"]" # os.environ.get('VAR_NAME')
|
|
87
|
+
pattern2 = r"os\.environ\[['\"]([A-Za-z0-9_]+)['\"]\]" # os.environ['VAR_NAME']
|
|
88
|
+
|
|
89
|
+
for line in result.stdout.splitlines():
|
|
90
|
+
for pattern in [pattern1, pattern2]:
|
|
91
|
+
for match in re.finditer(pattern, line):
|
|
92
|
+
var_name = match.group(1)
|
|
93
|
+
env_vars.add(var_name)
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"Error scanning directory {directory}: {e}")
|
|
97
|
+
|
|
98
|
+
return env_vars
|
|
99
|
+
|
|
100
|
+
@service()
|
|
101
|
+
async def scan_env_vars(params=None, context=None):
|
|
102
|
+
"""Scan all enabled plugins for environment variable references.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict: Dictionary with plugin names as keys and environment variable info as values
|
|
106
|
+
"""
|
|
107
|
+
results = {}
|
|
108
|
+
all_env_vars = set()
|
|
109
|
+
|
|
110
|
+
# Get all enabled plugins
|
|
111
|
+
enabled_plugins = list_enabled()
|
|
112
|
+
|
|
113
|
+
for plugin_name, category in enabled_plugins:
|
|
114
|
+
plugin_path = get_plugin_path(plugin_name)
|
|
115
|
+
if plugin_path:
|
|
116
|
+
# If plugin_path is a file, get its directory
|
|
117
|
+
if os.path.isfile(plugin_path):
|
|
118
|
+
plugin_path = os.path.dirname(plugin_path)
|
|
119
|
+
|
|
120
|
+
# Skip scanning if this is a directory we should ignore
|
|
121
|
+
if should_skip_directory(plugin_path):
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Scan the plugin directory for environment variable references
|
|
125
|
+
env_vars = scan_directory_for_env_vars(plugin_path)
|
|
126
|
+
|
|
127
|
+
if env_vars:
|
|
128
|
+
results[plugin_name] = {
|
|
129
|
+
'plugin_name': plugin_name,
|
|
130
|
+
'category': category,
|
|
131
|
+
'env_vars': list(env_vars)
|
|
132
|
+
}
|
|
133
|
+
all_env_vars.update(env_vars)
|
|
134
|
+
|
|
135
|
+
# Get current environment variables
|
|
136
|
+
current_env = {}
|
|
137
|
+
for var_name in all_env_vars:
|
|
138
|
+
if var_name in os.environ:
|
|
139
|
+
# Mask sensitive values
|
|
140
|
+
if any(sensitive in var_name.lower() for sensitive in ['key', 'secret', 'password', 'token']):
|
|
141
|
+
current_env[var_name] = '********'
|
|
142
|
+
else:
|
|
143
|
+
current_env[var_name] = os.environ[var_name]
|
|
144
|
+
else:
|
|
145
|
+
# Include variables not in environment with empty value
|
|
146
|
+
current_env[var_name] = ''
|
|
147
|
+
|
|
148
|
+
# Add current environment variables to results
|
|
149
|
+
results['current_env'] = current_env
|
|
150
|
+
|
|
151
|
+
return results
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def load_env_file():
|
|
155
|
+
"""Load environment variables from .env file.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
dict: Dictionary of environment variables from .env file
|
|
159
|
+
"""
|
|
160
|
+
env_vars = {}
|
|
161
|
+
env_file = '.env'
|
|
162
|
+
|
|
163
|
+
if os.path.exists(env_file):
|
|
164
|
+
with open(env_file, 'r') as f:
|
|
165
|
+
for line in f:
|
|
166
|
+
line = line.strip()
|
|
167
|
+
if line and not line.startswith('#'):
|
|
168
|
+
key, value = line.split('=', 1)
|
|
169
|
+
env_vars[key.strip()] = value.strip().strip('"\'')
|
|
170
|
+
|
|
171
|
+
return env_vars
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def save_env_file(env_vars):
|
|
175
|
+
"""Save environment variables to .env file.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
env_vars (dict): Dictionary of environment variables to save
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
bool: True if successful, False otherwise
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
# Load existing .env file to preserve variables not being updated
|
|
185
|
+
existing_vars = load_env_file()
|
|
186
|
+
|
|
187
|
+
# Update with new values
|
|
188
|
+
existing_vars.update(env_vars)
|
|
189
|
+
|
|
190
|
+
# Write to .env file
|
|
191
|
+
with open('.env', 'w') as f:
|
|
192
|
+
for key, value in existing_vars.items():
|
|
193
|
+
f.write(f"{key}={value}\n")
|
|
194
|
+
|
|
195
|
+
return True
|
|
196
|
+
except Exception as e:
|
|
197
|
+
print(f"Error saving .env file: {e}")
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@service()
|
|
202
|
+
async def update_env_var(var_name, var_value, context=None):
|
|
203
|
+
"""Update an environment variable.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
var_name (str): Name of the environment variable
|
|
207
|
+
var_value (str): Value to set
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
dict: Status of the operation
|
|
211
|
+
"""
|
|
212
|
+
try:
|
|
213
|
+
# Update the environment variable in the current process
|
|
214
|
+
os.environ[var_name] = var_value
|
|
215
|
+
|
|
216
|
+
# Save to .env file for persistence across restarts
|
|
217
|
+
save_success = save_env_file({var_name: var_value})
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
'success': True,
|
|
221
|
+
'message': f"Environment variable {var_name} updated successfully" +
|
|
222
|
+
(" and saved to .env file" if save_success else "")
|
|
223
|
+
}
|
|
224
|
+
except Exception as e:
|
|
225
|
+
return {
|
|
226
|
+
'success': False,
|
|
227
|
+
'message': f"Error updating environment variable: {str(e)}"
|
|
228
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from fastapi import APIRouter, Request, HTTPException, Body
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
from lib.route_decorators import requires_role
|
|
4
|
+
from .mod import scan_env_vars, update_env_var
|
|
5
|
+
|
|
6
|
+
# Create router with admin role requirement
|
|
7
|
+
router = APIRouter(
|
|
8
|
+
dependencies=[requires_role('admin')]
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
@router.get("/env_vars/scan")
|
|
12
|
+
async def get_env_vars(request: Request):
|
|
13
|
+
"""Scan all enabled plugins for environment variable references."""
|
|
14
|
+
try:
|
|
15
|
+
results = await scan_env_vars()
|
|
16
|
+
return JSONResponse({
|
|
17
|
+
"success": True,
|
|
18
|
+
"data": results
|
|
19
|
+
})
|
|
20
|
+
except Exception as e:
|
|
21
|
+
return JSONResponse({
|
|
22
|
+
"success": False,
|
|
23
|
+
"error": str(e)
|
|
24
|
+
}, status_code=500)
|
|
25
|
+
|
|
26
|
+
@router.post("/env_vars/update")
|
|
27
|
+
async def update_environment_var(
|
|
28
|
+
request: Request,
|
|
29
|
+
var_name: str = Body(...),
|
|
30
|
+
var_value: str = Body(...)
|
|
31
|
+
):
|
|
32
|
+
"""Update an environment variable."""
|
|
33
|
+
try:
|
|
34
|
+
result = await update_env_var(var_name, var_value)
|
|
35
|
+
return JSONResponse(result)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return JSONResponse({
|
|
38
|
+
"success": False,
|
|
39
|
+
"error": str(e)
|
|
40
|
+
}, status_code=500)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
width: 100%;
|
|
4
|
+
color: var(--text-color);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.env-manager {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
width: 100%;
|
|
11
|
+
max-width: 100%; /* Adjust to fit container */
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
gap: 20px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.section {
|
|
17
|
+
background: rgb(10, 10, 25);
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
h3 {
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
26
|
+
padding-bottom: 0.5rem;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
align-items: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.controls {
|
|
33
|
+
display: flex;
|
|
34
|
+
gap: 10px;
|
|
35
|
+
align-items: center;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.search-box {
|
|
39
|
+
background: rgba(0, 0, 0, 0.2);
|
|
40
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
41
|
+
color: var(--text-color);
|
|
42
|
+
padding: 0.5rem;
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
width: 200px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.env-table {
|
|
48
|
+
width: 100%;
|
|
49
|
+
border-collapse: collapse;
|
|
50
|
+
margin-top: 1rem;
|
|
51
|
+
table-layout: fixed; /* Fixed layout for better control */
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.env-table th,
|
|
55
|
+
.env-table td {
|
|
56
|
+
text-align: left;
|
|
57
|
+
padding: 0.75rem;
|
|
58
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Column widths */
|
|
62
|
+
.env-table th:nth-child(1),
|
|
63
|
+
.env-table td:nth-child(1) {
|
|
64
|
+
width: 40%; /* Variable Name */
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.env-table th:nth-child(2),
|
|
68
|
+
.env-table td:nth-child(2) {
|
|
69
|
+
width: 45%; /* Value */
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.env-table th:nth-child(3),
|
|
73
|
+
.env-table td:nth-child(3) {
|
|
74
|
+
width: 15%; /* Actions */
|
|
75
|
+
text-align: center;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.env-table th {
|
|
79
|
+
background: rgba(0, 0, 0, 0.2);
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.env-table tr:hover {
|
|
84
|
+
background: rgba(255, 255, 255, 0.03);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.plugin-header {
|
|
88
|
+
background: rgba(0, 0, 0, 0.2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.plugin-name {
|
|
92
|
+
font-weight: 600;
|
|
93
|
+
padding: 0.75rem;
|
|
94
|
+
color: #4a90e2;
|
|
95
|
+
font-size: 1.1em;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.plugin-var td {
|
|
99
|
+
padding-left: 1.5rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.var-name {
|
|
103
|
+
font-family: monospace;
|
|
104
|
+
background: rgba(0, 0, 0, 0.2);
|
|
105
|
+
padding: 0.25rem 0.5rem;
|
|
106
|
+
border-radius: 4px;
|
|
107
|
+
display: inline-block;
|
|
108
|
+
max-width: 100%;
|
|
109
|
+
word-break: break-all;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.var-value {
|
|
113
|
+
font-family: monospace;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
text-overflow: ellipsis;
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
display: inline-block;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.var-value.masked {
|
|
122
|
+
color: #888;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.actions {
|
|
126
|
+
display: flex;
|
|
127
|
+
gap: 5px;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
button {
|
|
132
|
+
background: #2a2a40;
|
|
133
|
+
color: #fff;
|
|
134
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
135
|
+
padding: 0.5rem 1rem;
|
|
136
|
+
border-radius: 4px;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
transition: background 0.2s;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
button:hover {
|
|
142
|
+
background: #3a3a50;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
button.primary {
|
|
146
|
+
background: #4a90e2;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
button.primary:hover {
|
|
150
|
+
background: #5aa0f2;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
button.small {
|
|
154
|
+
padding: 0.25rem 0.5rem;
|
|
155
|
+
font-size: 0.8rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.edit-form {
|
|
159
|
+
display: flex;
|
|
160
|
+
gap: 10px;
|
|
161
|
+
align-items: center;
|
|
162
|
+
margin-top: 0.5rem;
|
|
163
|
+
width: 100%;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.edit-form input {
|
|
167
|
+
background: rgba(0, 0, 0, 0.2);
|
|
168
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
169
|
+
color: var(--text-color);
|
|
170
|
+
padding: 0.5rem;
|
|
171
|
+
border-radius: 4px;
|
|
172
|
+
flex-grow: 1;
|
|
173
|
+
width: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.add-form {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 10px;
|
|
180
|
+
margin-top: 1rem;
|
|
181
|
+
padding: 1rem;
|
|
182
|
+
background: rgba(0, 0, 0, 0.1);
|
|
183
|
+
border-radius: 4px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.form-row {
|
|
187
|
+
display: flex;
|
|
188
|
+
gap: 10px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.form-row input {
|
|
192
|
+
background: rgba(0, 0, 0, 0.2);
|
|
193
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
194
|
+
color: var(--text-color);
|
|
195
|
+
padding: 0.5rem;
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
flex-grow: 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.form-actions {
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: flex-end;
|
|
203
|
+
gap: 10px;
|
|
204
|
+
margin-top: 0.5rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.plugin-refs {
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-wrap: wrap;
|
|
210
|
+
gap: 0.5rem;
|
|
211
|
+
max-width: 100%;
|
|
212
|
+
margin-top: 0.5rem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.plugin-tag {
|
|
216
|
+
font-family: monospace;
|
|
217
|
+
background: rgba(0, 0, 0, 0.2);
|
|
218
|
+
padding: 0.25rem 0.5rem;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
font-size: 0.8rem;
|
|
221
|
+
display: inline-block;
|
|
222
|
+
white-space: nowrap;
|
|
223
|
+
margin-bottom: 0.25rem;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.loading {
|
|
227
|
+
display: flex;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
align-items: center;
|
|
230
|
+
padding: 2rem;
|
|
231
|
+
font-style: italic;
|
|
232
|
+
color: #888;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.empty-state {
|
|
236
|
+
text-align: center;
|
|
237
|
+
padding: 2rem;
|
|
238
|
+
color: #888;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.material-icons {
|
|
242
|
+
font-size: 1.2rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Responsive adjustments */
|
|
246
|
+
@media (max-width: 768px) {
|
|
247
|
+
.env-table {
|
|
248
|
+
table-layout: auto;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.env-table th:nth-child(1),
|
|
252
|
+
.env-table td:nth-child(1),
|
|
253
|
+
.env-table th:nth-child(2),
|
|
254
|
+
.env-table td:nth-child(2),
|
|
255
|
+
.env-table th:nth-child(3),
|
|
256
|
+
.env-table td:nth-child(3) {
|
|
257
|
+
width: auto;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.plugin-var td {
|
|
261
|
+
padding-left: 0.75rem;
|
|
262
|
+
}
|
|
263
|
+
}
|