Akoda35 commited on
Commit
3b0a9a2
·
verified ·
1 Parent(s): 7ed7f5f

Upload 5 files

Browse files
templates/cpanel/environments/create.html ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Create Environment - PyRunner{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="flex">
7
+ {% include "cpanel/_sidebar.html" %}
8
+
9
+ <div class="flex-1 p-8">
10
+ <!-- Header -->
11
+ <div class="mb-8">
12
+ <nav class="flex items-center space-x-2 text-sm text-code-muted mb-4">
13
+ <a href="{% url 'cpanel:environment_list' %}" class="hover:text-code-accent">Environments</a>
14
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
16
+ </svg>
17
+ <span class="text-code-text">Create</span>
18
+ </nav>
19
+ <h1 class="text-2xl font-bold text-code-text">Create New Environment</h1>
20
+ <p class="text-code-muted">Create an isolated Python virtual environment</p>
21
+ </div>
22
+
23
+ <!-- Form -->
24
+ <div class="bg-code-surface border border-code-border rounded-xl p-6 max-w-2xl">
25
+ <form method="post">
26
+ {% csrf_token %}
27
+
28
+ <!-- Name -->
29
+ <div class="mb-6">
30
+ <label for="id_name" class="block text-sm font-medium text-code-text mb-2">
31
+ {{ form.name.label }}
32
+ </label>
33
+ {{ form.name }}
34
+ {% if form.name.errors %}
35
+ <p class="mt-1 text-sm text-code-red">{{ form.name.errors.0 }}</p>
36
+ {% endif %}
37
+ </div>
38
+
39
+ <!-- Description -->
40
+ <div class="mb-6">
41
+ <label for="id_description" class="block text-sm font-medium text-code-text mb-2">
42
+ {{ form.description.label }}
43
+ </label>
44
+ {{ form.description }}
45
+ {% if form.description.errors %}
46
+ <p class="mt-1 text-sm text-code-red">{{ form.description.errors.0 }}</p>
47
+ {% endif %}
48
+ </div>
49
+
50
+ <!-- Python Version -->
51
+ <div class="mb-6">
52
+ <label for="id_python_path" class="block text-sm font-medium text-code-text mb-2">
53
+ {{ form.python_path.label }}
54
+ </label>
55
+ {{ form.python_path }}
56
+ {% if form.python_path.help_text %}
57
+ <p class="mt-1 text-sm text-code-muted">{{ form.python_path.help_text }}</p>
58
+ {% endif %}
59
+ {% if form.python_path.errors %}
60
+ <p class="mt-1 text-sm text-code-red">{{ form.python_path.errors.0 }}</p>
61
+ {% endif %}
62
+ </div>
63
+
64
+ <!-- Info Box -->
65
+ <div class="mb-6 p-4 bg-code-bg border border-code-border rounded-lg">
66
+ <div class="flex items-start space-x-3">
67
+ <svg class="w-5 h-5 text-code-accent mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
68
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
69
+ </svg>
70
+ <div class="text-sm text-code-muted">
71
+ <p class="font-medium text-code-text">What happens when you create an environment?</p>
72
+ <ul class="mt-2 space-y-1 list-disc list-inside">
73
+ <li>A new Python virtual environment will be created</li>
74
+ <li>The environment will be isolated from other environments</li>
75
+ <li>You can install packages specific to this environment</li>
76
+ </ul>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- Actions -->
82
+ <div class="flex items-center justify-end space-x-4 pt-6 border-t border-code-border">
83
+ <a href="{% url 'cpanel:environment_list' %}"
84
+ class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors">
85
+ Cancel
86
+ </a>
87
+ <button type="submit"
88
+ class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors">
89
+ Create Environment
90
+ </button>
91
+ </div>
92
+ </form>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ {% endblock %}
templates/cpanel/environments/detail.html ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ environment.name }} - PyRunner{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="flex">
7
+ {% include "cpanel/_sidebar.html" %}
8
+
9
+ <div class="flex-1 p-8">
10
+ <!-- Header -->
11
+ <div class="mb-8">
12
+ <nav class="flex items-center space-x-2 text-sm text-code-muted mb-4">
13
+ <a href="{% url 'cpanel:environment_list' %}" class="hover:text-code-accent">Environments</a>
14
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
16
+ </svg>
17
+ <span class="text-code-text">{{ environment.name }}</span>
18
+ </nav>
19
+
20
+ <div class="flex items-start justify-between">
21
+ <div>
22
+ <h1 class="text-2xl font-bold text-code-text flex items-center">
23
+ {{ environment.name }}
24
+ {% if environment.is_default %}
25
+ <span class="ml-3 px-2 py-1 text-xs rounded bg-code-accent/20 text-code-accent">Default</span>
26
+ {% endif %}
27
+ {% if environment.is_active %}
28
+ <span class="ml-2 px-2 py-1 text-xs rounded bg-code-green/20 text-code-green">Active</span>
29
+ {% else %}
30
+ <span class="ml-2 px-2 py-1 text-xs rounded bg-code-muted/20 text-code-muted">Inactive</span>
31
+ {% endif %}
32
+ </h1>
33
+ {% if environment.description %}
34
+ <p class="text-code-muted mt-1">{{ environment.description }}</p>
35
+ {% endif %}
36
+ </div>
37
+ <div class="flex items-center space-x-3">
38
+ <a href="{% url 'cpanel:environment_packages' pk=environment.pk %}"
39
+ class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors inline-flex items-center">
40
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
42
+ </svg>
43
+ Manage Packages
44
+ </a>
45
+ <a href="{% url 'cpanel:environment_edit' pk=environment.pk %}"
46
+ class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors">
47
+ Edit
48
+ </a>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
54
+ <!-- Left Column: Scripts -->
55
+ <div class="lg:col-span-2">
56
+ <h2 class="text-lg font-semibold text-code-text mb-4">Scripts Using This Environment</h2>
57
+
58
+ {% if scripts %}
59
+ <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden">
60
+ <table class="w-full">
61
+ <thead class="bg-code-bg border-b border-code-border">
62
+ <tr>
63
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Name</th>
64
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Status</th>
65
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Runs</th>
66
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Updated</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody class="divide-y divide-code-border">
70
+ {% for script in scripts %}
71
+ <tr class="hover:bg-code-bg/50 transition-colors">
72
+ <td class="px-6 py-4">
73
+ <a href="{% url 'cpanel:script_detail' pk=script.pk %}" class="text-code-accent hover:underline font-medium">
74
+ {{ script.name }}
75
+ </a>
76
+ </td>
77
+ <td class="px-6 py-4">
78
+ {% if script.is_enabled %}
79
+ <span class="px-2 py-1 text-xs rounded bg-code-green/20 text-code-green">Enabled</span>
80
+ {% else %}
81
+ <span class="px-2 py-1 text-xs rounded bg-code-yellow/20 text-code-yellow">Disabled</span>
82
+ {% endif %}
83
+ </td>
84
+ <td class="px-6 py-4 text-code-text">
85
+ {{ script.run_count }}
86
+ </td>
87
+ <td class="px-6 py-4 text-code-muted text-sm">
88
+ {{ script.updated_at|timesince }} ago
89
+ </td>
90
+ </tr>
91
+ {% endfor %}
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ {% else %}
96
+ <div class="bg-code-surface border border-code-border rounded-xl p-8 text-center">
97
+ <p class="text-code-muted">No scripts are using this environment yet.</p>
98
+ <a href="{% url 'cpanel:script_create' %}"
99
+ class="inline-flex items-center mt-4 px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors">
100
+ Create a Script
101
+ </a>
102
+ </div>
103
+ {% endif %}
104
+ </div>
105
+
106
+ <!-- Right Column: Details -->
107
+ <div class="space-y-6">
108
+ <!-- Environment Info -->
109
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
110
+ <h3 class="text-sm font-semibold text-code-text mb-4">Environment Info</h3>
111
+ <dl class="space-y-4">
112
+ <div>
113
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Python Version</dt>
114
+ <dd class="text-code-text mt-1 font-mono">{{ environment.python_version }}</dd>
115
+ </div>
116
+ <div>
117
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Path</dt>
118
+ <dd class="text-code-text mt-1 font-mono text-sm break-all">{{ environment.path }}</dd>
119
+ </div>
120
+ <div>
121
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Script Count</dt>
122
+ <dd class="text-code-text mt-1">{{ environment.script_count }}</dd>
123
+ </div>
124
+ <div>
125
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Disk Usage</dt>
126
+ <dd class="text-code-text mt-1">{{ disk_usage }}</dd>
127
+ </div>
128
+ <div>
129
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Created</dt>
130
+ <dd class="text-code-text mt-1">{{ environment.created_at|date:"M d, Y H:i" }}</dd>
131
+ </div>
132
+ {% if environment.created_by %}
133
+ <div>
134
+ <dt class="text-xs text-code-muted uppercase tracking-wider">Created By</dt>
135
+ <dd class="text-code-text mt-1">{{ environment.created_by.email }}</dd>
136
+ </div>
137
+ {% endif %}
138
+ </dl>
139
+ </div>
140
+
141
+ <!-- Status -->
142
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
143
+ <h3 class="text-sm font-semibold text-code-text mb-4">Status</h3>
144
+ <div class="space-y-3">
145
+ <div class="flex items-center justify-between">
146
+ <span class="text-code-muted">Environment Exists</span>
147
+ {% if environment.exists %}
148
+ <span class="px-2 py-1 text-xs rounded bg-code-green/20 text-code-green">Yes</span>
149
+ {% else %}
150
+ <span class="px-2 py-1 text-xs rounded bg-code-red/20 text-code-red">No</span>
151
+ {% endif %}
152
+ </div>
153
+ <div class="flex items-center justify-between">
154
+ <span class="text-code-muted">Python Exists</span>
155
+ {% if environment.python_exists %}
156
+ <span class="px-2 py-1 text-xs rounded bg-code-green/20 text-code-green">Yes</span>
157
+ {% else %}
158
+ <span class="px-2 py-1 text-xs rounded bg-code-red/20 text-code-red">No</span>
159
+ {% endif %}
160
+ </div>
161
+ <div class="flex items-center justify-between">
162
+ <span class="text-code-muted">Can Delete</span>
163
+ {% if environment.can_delete %}
164
+ <span class="px-2 py-1 text-xs rounded bg-code-yellow/20 text-code-yellow">Yes</span>
165
+ {% else %}
166
+ <span class="px-2 py-1 text-xs rounded bg-code-muted/20 text-code-muted">No</span>
167
+ {% endif %}
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <!-- Actions -->
173
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
174
+ <h3 class="text-sm font-semibold text-code-text mb-4">Actions</h3>
175
+ <div class="space-y-3">
176
+ {% if not environment.is_default %}
177
+ <form method="post" action="{% url 'cpanel:environment_set_default' pk=environment.pk %}">
178
+ {% csrf_token %}
179
+ <button type="submit"
180
+ class="w-full px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors text-sm">
181
+ Set as Default
182
+ </button>
183
+ </form>
184
+ {% endif %}
185
+
186
+ {% if environment.can_delete %}
187
+ <form method="post" action="{% url 'cpanel:environment_delete' pk=environment.pk %}"
188
+ onsubmit="return confirm('Are you sure you want to delete this environment? This will remove the virtual environment folder and cannot be undone.');">
189
+ {% csrf_token %}
190
+ <button type="submit"
191
+ class="w-full px-4 py-2 bg-code-red/10 border border-code-red/30 hover:bg-code-red/20 text-code-red rounded-lg transition-colors text-sm">
192
+ Delete Environment
193
+ </button>
194
+ </form>
195
+ {% else %}
196
+ <div class="p-3 bg-code-bg rounded-lg">
197
+ <p class="text-xs text-code-muted">
198
+ {% if environment.is_default %}
199
+ Cannot delete the default environment.
200
+ {% else %}
201
+ Cannot delete: {{ environment.script_count }} script(s) are using this environment.
202
+ {% endif %}
203
+ </p>
204
+ </div>
205
+ {% endif %}
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Requirements -->
210
+ {% if environment.requirements %}
211
+ <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden">
212
+ <div class="px-4 py-3 border-b border-code-border bg-code-bg flex items-center justify-between">
213
+ <span class="text-sm font-medium text-code-text">Requirements</span>
214
+ <a href="{% url 'cpanel:environment_packages' pk=environment.pk %}"
215
+ class="text-xs text-code-accent hover:underline">Manage</a>
216
+ </div>
217
+ <div class="p-4 max-h-64 overflow-y-auto">
218
+ <pre class="text-code-text font-mono text-xs whitespace-pre-wrap">{{ environment.requirements }}</pre>
219
+ </div>
220
+ </div>
221
+ {% endif %}
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ {% endblock %}
templates/cpanel/environments/edit.html ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Edit {{ environment.name }} - PyRunner{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="flex">
7
+ {% include "cpanel/_sidebar.html" %}
8
+
9
+ <div class="flex-1 p-8">
10
+ <!-- Header -->
11
+ <div class="mb-8">
12
+ <nav class="flex items-center space-x-2 text-sm text-code-muted mb-4">
13
+ <a href="{% url 'cpanel:environment_list' %}" class="hover:text-code-accent">Environments</a>
14
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
16
+ </svg>
17
+ <a href="{% url 'cpanel:environment_detail' pk=environment.pk %}" class="hover:text-code-accent">{{ environment.name }}</a>
18
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
20
+ </svg>
21
+ <span class="text-code-text">Edit</span>
22
+ </nav>
23
+ <h1 class="text-2xl font-bold text-code-text">Edit Environment</h1>
24
+ <p class="text-code-muted">Update environment name and description</p>
25
+ </div>
26
+
27
+ <!-- Form -->
28
+ <div class="bg-code-surface border border-code-border rounded-xl p-6 max-w-2xl">
29
+ <form method="post">
30
+ {% csrf_token %}
31
+
32
+ <!-- Name -->
33
+ <div class="mb-6">
34
+ <label for="id_name" class="block text-sm font-medium text-code-text mb-2">
35
+ {{ form.name.label }}
36
+ </label>
37
+ {{ form.name }}
38
+ {% if form.name.errors %}
39
+ <p class="mt-1 text-sm text-code-red">{{ form.name.errors.0 }}</p>
40
+ {% endif %}
41
+ </div>
42
+
43
+ <!-- Description -->
44
+ <div class="mb-6">
45
+ <label for="id_description" class="block text-sm font-medium text-code-text mb-2">
46
+ {{ form.description.label }}
47
+ </label>
48
+ {{ form.description }}
49
+ {% if form.description.errors %}
50
+ <p class="mt-1 text-sm text-code-red">{{ form.description.errors.0 }}</p>
51
+ {% endif %}
52
+ </div>
53
+
54
+ <!-- Read-only Info -->
55
+ <div class="mb-6 p-4 bg-code-bg border border-code-border rounded-lg">
56
+ <h3 class="text-sm font-medium text-code-text mb-3">Environment Details (Read-only)</h3>
57
+ <dl class="space-y-2 text-sm">
58
+ <div class="flex justify-between">
59
+ <dt class="text-code-muted">Python Version:</dt>
60
+ <dd class="text-code-text font-mono">{{ environment.python_version }}</dd>
61
+ </div>
62
+ <div class="flex justify-between">
63
+ <dt class="text-code-muted">Path:</dt>
64
+ <dd class="text-code-text font-mono">{{ environment.path }}</dd>
65
+ </div>
66
+ <div class="flex justify-between">
67
+ <dt class="text-code-muted">Scripts:</dt>
68
+ <dd class="text-code-text">{{ environment.script_count }}</dd>
69
+ </div>
70
+ </dl>
71
+ </div>
72
+
73
+ <!-- Actions -->
74
+ <div class="flex items-center justify-end space-x-4 pt-6 border-t border-code-border">
75
+ <a href="{% url 'cpanel:environment_detail' pk=environment.pk %}"
76
+ class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors">
77
+ Cancel
78
+ </a>
79
+ <button type="submit"
80
+ class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors">
81
+ Save Changes
82
+ </button>
83
+ </div>
84
+ </form>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ {% endblock %}
templates/cpanel/environments/list.html ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Environments - PyRunner{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="flex">
7
+ {% include "cpanel/_sidebar.html" %}
8
+
9
+ <div class="flex-1 p-8">
10
+ <!-- Header -->
11
+ <div class="flex items-start justify-between mb-8">
12
+ <div>
13
+ <h1 class="text-2xl font-bold text-code-text">Environments</h1>
14
+ <p class="text-code-muted">Python virtual environments for running scripts</p>
15
+ </div>
16
+ <a href="{% url 'cpanel:environment_create' %}"
17
+ class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors inline-flex items-center">
18
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
20
+ </svg>
21
+ Create Environment
22
+ </a>
23
+ </div>
24
+
25
+ <!-- Environments Grid -->
26
+ {% if environments %}
27
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
28
+ {% for env in environments %}
29
+ <a href="{% url 'cpanel:environment_detail' pk=env.pk %}"
30
+ class="bg-code-surface border border-code-border rounded-xl p-6 hover:border-code-accent transition-colors block">
31
+ <div class="flex items-start justify-between mb-4">
32
+ <div class="flex items-center">
33
+ <div class="w-10 h-10 bg-code-accent/20 rounded-lg flex items-center justify-center mr-3">
34
+ <svg class="w-5 h-5 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
36
+ </svg>
37
+ </div>
38
+ <div>
39
+ <h3 class="font-semibold text-code-text">{{ env.name }}</h3>
40
+ <p class="text-xs text-code-muted">Python {{ env.python_version }}</p>
41
+ </div>
42
+ </div>
43
+ <div class="flex flex-col items-end space-y-1">
44
+ {% if env.is_default %}
45
+ <span class="px-2 py-1 text-xs rounded bg-code-accent/20 text-code-accent">Default</span>
46
+ {% endif %}
47
+ {% if env.is_active %}
48
+ <span class="px-2 py-1 text-xs rounded bg-code-green/20 text-code-green">Active</span>
49
+ {% else %}
50
+ <span class="px-2 py-1 text-xs rounded bg-code-muted/20 text-code-muted">Inactive</span>
51
+ {% endif %}
52
+ </div>
53
+ </div>
54
+
55
+ {% if env.description %}
56
+ <p class="text-code-muted text-sm mb-4 line-clamp-2">{{ env.description }}</p>
57
+ {% endif %}
58
+
59
+ <div class="flex items-center justify-between text-sm">
60
+ <span class="text-code-muted">{{ env.script_count }} script{{ env.script_count|pluralize }}</span>
61
+ <span class="text-code-muted">{{ env.created_at|date:"M d, Y" }}</span>
62
+ </div>
63
+ </a>
64
+ {% endfor %}
65
+ </div>
66
+ {% else %}
67
+ <div class="bg-code-surface border border-code-border rounded-xl p-12 text-center">
68
+ <div class="w-16 h-16 bg-code-accent/20 rounded-full flex items-center justify-center mx-auto mb-4">
69
+ <svg class="w-8 h-8 text-code-accent" fill="none" stroke="currentColor" viewBox="0 0 24 24">
70
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
71
+ </svg>
72
+ </div>
73
+ <h3 class="text-lg font-semibold text-code-text mb-2">No environments yet</h3>
74
+ <p class="text-code-muted mb-6">Create a new environment to get started, or run the setup command.</p>
75
+ <div class="flex flex-col items-center space-y-4">
76
+ <a href="{% url 'cpanel:environment_create' %}"
77
+ class="px-6 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors inline-flex items-center">
78
+ <svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
79
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
80
+ </svg>
81
+ Create Environment
82
+ </a>
83
+ <span class="text-code-muted text-sm">or</span>
84
+ <div class="bg-code-bg border border-code-border rounded-lg p-4 max-w-md">
85
+ <code class="text-code-accent text-sm font-mono">python manage.py setup_default_env</code>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ {% endif %}
90
+ </div>
91
+ </div>
92
+ {% endblock %}
templates/cpanel/environments/packages.html ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Packages - {{ environment.name }} - PyRunner{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="flex">
7
+ {% include "cpanel/_sidebar.html" %}
8
+
9
+ <div class="flex-1 p-8">
10
+ <!-- Header -->
11
+ <div class="mb-8">
12
+ <nav class="flex items-center space-x-2 text-sm text-code-muted mb-4">
13
+ <a href="{% url 'cpanel:environment_list' %}" class="hover:text-code-accent">Environments</a>
14
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
15
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
16
+ </svg>
17
+ <a href="{% url 'cpanel:environment_detail' pk=environment.pk %}" class="hover:text-code-accent">{{ environment.name }}</a>
18
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
19
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
20
+ </svg>
21
+ <span class="text-code-text">Packages</span>
22
+ </nav>
23
+
24
+ <div class="flex items-start justify-between">
25
+ <div>
26
+ <h1 class="text-2xl font-bold text-code-text">Package Management</h1>
27
+ <p class="text-code-muted">{{ environment.name }} &middot; {{ package_count }} packages installed</p>
28
+ </div>
29
+ <div class="flex items-center space-x-3">
30
+ <a href="{% url 'cpanel:export_requirements' pk=environment.pk %}"
31
+ class="px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors inline-flex items-center">
32
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
33
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
34
+ </svg>
35
+ Export requirements.txt
36
+ </a>
37
+ </div>
38
+ </div>
39
+ </div>
40
+
41
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
42
+ <!-- Left Column: Package List -->
43
+ <div class="lg:col-span-2 space-y-6">
44
+ <!-- Search and Sort -->
45
+ <div class="bg-code-surface border border-code-border rounded-xl p-4">
46
+ <form method="get" class="flex items-center space-x-4">
47
+ <div class="flex-1">
48
+ <input type="text" name="search" value="{{ search }}"
49
+ placeholder="Search packages..."
50
+ class="w-full px-4 py-2 bg-code-bg border border-code-border rounded-lg text-code-text placeholder-code-muted/50 focus:outline-none focus:ring-2 focus:ring-code-accent/50">
51
+ </div>
52
+ <select name="sort"
53
+ onchange="this.form.submit()"
54
+ class="px-4 py-2 bg-code-bg border border-code-border rounded-lg text-code-text focus:outline-none focus:ring-2 focus:ring-code-accent/50">
55
+ <option value="name" {% if sort_by == "name" %}selected{% endif %}>Sort by Name</option>
56
+ <option value="version" {% if sort_by == "version" %}selected{% endif %}>Sort by Version</option>
57
+ </select>
58
+ <button type="submit"
59
+ class="px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white rounded-lg transition-colors">
60
+ Search
61
+ </button>
62
+ </form>
63
+ </div>
64
+
65
+ <!-- Package Table -->
66
+ <div class="bg-code-surface border border-code-border rounded-xl overflow-hidden">
67
+ {% if packages %}
68
+ <table class="w-full">
69
+ <thead class="bg-code-bg border-b border-code-border">
70
+ <tr>
71
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Package</th>
72
+ <th class="px-6 py-3 text-left text-xs font-semibold text-code-muted uppercase tracking-wider">Version</th>
73
+ <th class="px-6 py-3 text-right text-xs font-semibold text-code-muted uppercase tracking-wider">Actions</th>
74
+ </tr>
75
+ </thead>
76
+ <tbody class="divide-y divide-code-border">
77
+ {% for package in packages %}
78
+ <tr class="hover:bg-code-bg/50 transition-colors">
79
+ <td class="px-6 py-4">
80
+ <span class="text-code-text font-mono">{{ package.name }}</span>
81
+ </td>
82
+ <td class="px-6 py-4">
83
+ <span class="text-code-muted font-mono text-sm">{{ package.version }}</span>
84
+ </td>
85
+ <td class="px-6 py-4 text-right">
86
+ <form method="post" action="{% url 'cpanel:package_uninstall' pk=environment.pk %}"
87
+ onsubmit="return confirm('Uninstall {{ package.name }}?');"
88
+ class="inline">
89
+ {% csrf_token %}
90
+ <input type="hidden" name="package_name" value="{{ package.name }}">
91
+ <button type="submit"
92
+ class="text-code-red hover:text-code-red/80 text-sm transition-colors">
93
+ Uninstall
94
+ </button>
95
+ </form>
96
+ </td>
97
+ </tr>
98
+ {% endfor %}
99
+ </tbody>
100
+ </table>
101
+ {% else %}
102
+ <div class="p-8 text-center">
103
+ <svg class="w-12 h-12 text-code-muted mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
105
+ </svg>
106
+ <p class="text-code-muted">
107
+ {% if search %}
108
+ No packages found matching "{{ search }}"
109
+ {% else %}
110
+ No packages installed yet
111
+ {% endif %}
112
+ </p>
113
+ </div>
114
+ {% endif %}
115
+ </div>
116
+ </div>
117
+
118
+ <!-- Right Column: Install Forms and Operations -->
119
+ <div class="space-y-6">
120
+ <!-- Install Single Package -->
121
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
122
+ <h3 class="text-sm font-semibold text-code-text mb-4">Install Package</h3>
123
+ <form method="post" action="{% url 'cpanel:package_install' pk=environment.pk %}">
124
+ {% csrf_token %}
125
+ <div class="mb-4">
126
+ {{ install_form.package_spec }}
127
+ </div>
128
+ <p class="text-xs text-code-muted mb-4">
129
+ Examples: requests, django>=4.0, numpy==1.24.0
130
+ </p>
131
+ <button type="submit"
132
+ class="w-full px-4 py-2 bg-code-accent hover:bg-code-accent/90 text-white font-semibold rounded-lg transition-colors">
133
+ Install
134
+ </button>
135
+ </form>
136
+ </div>
137
+
138
+ <!-- Bulk Install -->
139
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
140
+ <h3 class="text-sm font-semibold text-code-text mb-4">Bulk Install</h3>
141
+ <form method="post" action="{% url 'cpanel:bulk_install' pk=environment.pk %}" enctype="multipart/form-data">
142
+ {% csrf_token %}
143
+ <div class="mb-4">
144
+ <label class="block text-xs text-code-muted mb-2">Paste requirements.txt content:</label>
145
+ {{ bulk_form.requirements }}
146
+ </div>
147
+ <div class="mb-4">
148
+ <label class="block text-xs text-code-muted mb-2">Or upload file:</label>
149
+ {{ bulk_form.requirements_file }}
150
+ </div>
151
+ <button type="submit"
152
+ class="w-full px-4 py-2 bg-code-surface border border-code-border hover:border-code-accent text-code-text rounded-lg transition-colors">
153
+ Install from Requirements
154
+ </button>
155
+ </form>
156
+ </div>
157
+
158
+ <!-- Recent Operations -->
159
+ {% if operations %}
160
+ <div class="bg-code-surface border border-code-border rounded-xl p-6">
161
+ <h3 class="text-sm font-semibold text-code-text mb-4">Recent Operations</h3>
162
+ <div class="space-y-3">
163
+ {% for op in operations %}
164
+ <div class="p-3 bg-code-bg rounded-lg" data-operation-id="{{ op.id }}">
165
+ <div class="flex items-center justify-between mb-1">
166
+ <span class="text-xs font-medium text-code-text">
167
+ {{ op.get_operation_display }}
168
+ </span>
169
+ {% if op.status == "pending" %}
170
+ <span class="px-2 py-0.5 text-xs rounded bg-code-yellow/20 text-code-yellow">Pending</span>
171
+ {% elif op.status == "running" %}
172
+ <span class="px-2 py-0.5 text-xs rounded bg-code-accent/20 text-code-accent">Running</span>
173
+ {% elif op.status == "success" %}
174
+ <span class="px-2 py-0.5 text-xs rounded bg-code-green/20 text-code-green">Success</span>
175
+ {% elif op.status == "failed" %}
176
+ <span class="px-2 py-0.5 text-xs rounded bg-code-red/20 text-code-red">Failed</span>
177
+ {% endif %}
178
+ </div>
179
+ <p class="text-xs text-code-muted font-mono truncate" title="{{ op.package_spec }}">
180
+ {% if op.operation == "bulk_install" %}
181
+ {{ op.package_spec|linebreaksbr|truncatewords:10 }}
182
+ {% else %}
183
+ {{ op.package_spec }}
184
+ {% endif %}
185
+ </p>
186
+ {% if op.error %}
187
+ <p class="text-xs text-code-red mt-1 truncate" title="{{ op.error }}">
188
+ {{ op.error|truncatechars:50 }}
189
+ </p>
190
+ {% endif %}
191
+ <p class="text-xs text-code-muted mt-1">
192
+ {{ op.created_at|timesince }} ago
193
+ {% if op.duration %}
194
+ &middot; {{ op.duration_display }}
195
+ {% endif %}
196
+ </p>
197
+ </div>
198
+ {% endfor %}
199
+ </div>
200
+ </div>
201
+ {% endif %}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ {% if has_running_operation %}
208
+ <script>
209
+ // Poll for operation status updates
210
+ (function() {
211
+ function refreshPage() {
212
+ location.reload();
213
+ }
214
+ // Refresh every 3 seconds while operations are running
215
+ setTimeout(refreshPage, 3000);
216
+ })();
217
+ </script>
218
+ {% endif %}
219
+ {% endblock %}