Compare commits
8 Commits
65868c043e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
e537ec2ac0
|
|||
|
ec102dd1cc
|
|||
|
7bae0d12de
|
|||
|
dbfb38bb8a
|
|||
|
20239242ce
|
|||
|
60e13822ee
|
|||
|
4d492ded4e
|
|||
|
3b53967c40
|
40
AGENTS.md
40
AGENTS.md
@@ -209,6 +209,11 @@ labhelper/
|
|||||||
│ │ ├── clean_orphaned_files.py # Cleanup orphaned ThingFile attachments
|
│ │ ├── clean_orphaned_files.py # Cleanup orphaned ThingFile attachments
|
||||||
│ │ └── clean_orphaned_images.py # Cleanup orphaned Thing images
|
│ │ └── clean_orphaned_images.py # Cleanup orphaned Thing images
|
||||||
│ ├── migrations/ # Database migrations
|
│ ├── migrations/ # Database migrations
|
||||||
|
│ ├── static/
|
||||||
|
│ │ └── css/
|
||||||
|
│ │ ├── base.css # Base styles (layout, navbar, buttons, alerts, etc.)
|
||||||
|
│ │ ├── edit_thing.css # Edit thing form styles
|
||||||
|
│ │ └── thing_detail.css # Markdown content + lightbox styles
|
||||||
│ ├── templates/
|
│ ├── templates/
|
||||||
│ │ └── boxes/
|
│ │ └── boxes/
|
||||||
│ │ ├── add_things.html # Form to add multiple things
|
│ │ ├── add_things.html # Form to add multiple things
|
||||||
@@ -356,14 +361,21 @@ The project uses a base template system at `labhelper/templates/base.html`. All
|
|||||||
|
|
||||||
### CSS Guidelines
|
### CSS Guidelines
|
||||||
|
|
||||||
|
**Static CSS Files:**
|
||||||
|
- Base styles live in `boxes/static/css/base.css` (loaded by `base.html` via `{% static %}`)
|
||||||
|
- Page-specific styles live in separate static CSS files (e.g., `thing_detail.css`, `edit_thing.css`)
|
||||||
|
- Child templates load their CSS via `{% block extra_css %}` with `<link>` tags
|
||||||
|
- WhiteNoise serves and cache-busts static files via `CompressedManifestStaticFilesStorage`
|
||||||
|
- Run `python manage.py collectstatic` after adding or modifying static CSS files
|
||||||
|
|
||||||
**Naming:**
|
**Naming:**
|
||||||
- Use descriptive class names
|
- Use descriptive class names
|
||||||
- BEM pattern encouraged for complex components
|
- BEM pattern encouraged for complex components
|
||||||
- Inline styles allowed for template-specific styling
|
- Inline styles allowed for template-specific one-off styling
|
||||||
|
|
||||||
**Styles:**
|
**Styles:**
|
||||||
- Use base template styles when possible
|
- Use base CSS classes when possible
|
||||||
- Template-specific styles in `{% block extra_css %}`
|
- Page-specific styles in dedicated static CSS files loaded via `{% block extra_css %}`
|
||||||
- JavaScript in `{% block extra_js %}`
|
- JavaScript in `{% block extra_js %}`
|
||||||
- Smooth transitions (0.2s - 0.3s)
|
- Smooth transitions (0.2s - 0.3s)
|
||||||
- Hover effects with transform and box-shadow
|
- Hover effects with transform and box-shadow
|
||||||
@@ -410,36 +422,44 @@ The project uses a base template system at `labhelper/templates/base.html`. All
|
|||||||
- `title`: Page title tag
|
- `title`: Page title tag
|
||||||
- `page_header`: Page header with breadcrumbs
|
- `page_header`: Page header with breadcrumbs
|
||||||
- `content`: Main page content
|
- `content`: Main page content
|
||||||
- `extra_css`: Additional styles
|
- `extra_css`: Additional CSS via `<link>` tags to static files
|
||||||
|
- `extra_head`: Additional head elements
|
||||||
- `extra_js`: Additional JavaScript
|
- `extra_js`: Additional JavaScript
|
||||||
|
|
||||||
3. **Load required template tags**
|
3. **Load required template tags**
|
||||||
```django
|
```django
|
||||||
{% load static %}
|
{% load static %} {# Required when using {% static %} for CSS/asset links #}
|
||||||
{% load mptt_tags %}
|
{% load mptt_tags %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
{% load dict_extras %}
|
{% load dict_extras %}
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Use URL names for links**
|
4. **Link page-specific CSS from static files**
|
||||||
|
```django
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/thing_detail.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Use URL names for links**
|
||||||
```django
|
```django
|
||||||
<a href="{% url 'box_detail' box.id %}">
|
<a href="{% url 'box_detail' box.id %}">
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Use icons with Font Awesome**
|
6. **Use icons with Font Awesome**
|
||||||
```django
|
```django
|
||||||
<i class="fas fa-box"></i>
|
<i class="fas fa-box"></i>
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Add breadcrumbs for navigation**
|
7. **Add breadcrumbs for navigation**
|
||||||
```django
|
```django
|
||||||
<p class="breadcrumb">
|
<p class="breadcrumb">
|
||||||
<a href="/"><i class="fas fa-home"></i> Home</a> /
|
<a href="/"><i class="fas fa-home"></i> Home</a> /
|
||||||
<a href="/box/{{ box.id }}/"><i class="fas fa-box"></i> Box {{ box.id }}</a>
|
<a href="/box/{{ box.id }}/"><i class="fas fa-box"></i> Box {{ box.id }}</a>
|
||||||
</p>
|
</p>
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **Icon alignment in lists**: When using icons in list items, use fixed width containers to ensure proper alignment
|
8. **Icon alignment in lists**: When using icons in list items, use fixed width containers to ensure proper alignment
|
||||||
```django
|
```django
|
||||||
<a href="{% url 'thing_detail' thing.id %}" style="display: inline-block; width: 20px; text-align: center;">
|
<a href="{% url 'thing_detail' thing.id %}" style="display: inline-block; width: 20px; text-align: center;">
|
||||||
<i class="fas fa-link"></i>
|
<i class="fas fa-link"></i>
|
||||||
|
|||||||
@@ -17,6 +17,5 @@ data:
|
|||||||
LOGIN_URL: "login"
|
LOGIN_URL: "login"
|
||||||
LOGIN_REDIRECT_URL: "index"
|
LOGIN_REDIRECT_URL: "index"
|
||||||
LOGOUT_REDIRECT_URL: "login"
|
LOGOUT_REDIRECT_URL: "login"
|
||||||
TRUSTED_PROXIES: "192.168.17.44,192.168.17.53"
|
|
||||||
GUNICORN_OPTS: "--access-logfile -"
|
GUNICORN_OPTS: "--access-logfile -"
|
||||||
IMAGE_TAG: "0.070"
|
IMAGE_TAG: "0.076"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ spec:
|
|||||||
mountPath: /data
|
mountPath: /data
|
||||||
containers:
|
containers:
|
||||||
- name: web
|
- name: web
|
||||||
image: git.baumann.gr/adebaumann/labhelper:0.070
|
image: git.baumann.gr/adebaumann/labhelper:0.076
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8000
|
- containerPort: 8000
|
||||||
@@ -102,11 +102,6 @@ spec:
|
|||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
name: django-config
|
name: django-config
|
||||||
key: LOGOUT_REDIRECT_URL
|
key: LOGOUT_REDIRECT_URL
|
||||||
- name: TRUSTED_PROXIES
|
|
||||||
valueFrom:
|
|
||||||
configMapKeyRef:
|
|
||||||
name: django-config
|
|
||||||
key: TRUSTED_PROXIES
|
|
||||||
- name: GUNICORN_OPTS
|
- name: GUNICORN_OPTS
|
||||||
valueFrom:
|
valueFrom:
|
||||||
configMapKeyRef:
|
configMapKeyRef:
|
||||||
|
|||||||
383
boxes/static/css/base.css
Normal file
383
boxes/static/css/base.css
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 15px 30px;
|
||||||
|
margin: 20px auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand i {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #555;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggle:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a,
|
||||||
|
.navbar-nav form {
|
||||||
|
color: #555;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a:hover,
|
||||||
|
.navbar-nav button:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a i {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #555;
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.navbar {
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav {
|
||||||
|
display: none;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a,
|
||||||
|
.navbar-nav form {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav a:first-child,
|
||||||
|
.navbar-nav form:first-child {
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav > a:last-child {
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:last-child {
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-nav button {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header h1 {
|
||||||
|
color: #333;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb {
|
||||||
|
color: #888;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header .breadcrumb a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 3px solid #667eea;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section h2 i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(127, 140, 141, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
box-shadow: 0 6px 20px rgba(127, 140, 141, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: linear-gradient(135deg, #00b894 0%, #00cec9 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-error {
|
||||||
|
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
min-width: 200px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
z-index: 1000;
|
||||||
|
top: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 20px;
|
||||||
|
color: #555;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:first-child {
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:last-child {
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logout-form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content,
|
||||||
|
.dropdown:focus-within .dropdown-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn:hover {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dropdown {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content,
|
||||||
|
.dropdown:hover .dropdown-content,
|
||||||
|
.dropdown:focus-within .dropdown-content {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
boxes/static/css/edit_thing.css
Normal file
21
boxes/static/css/edit_thing.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#id_name, #id_description {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border: 2px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
#id_name:focus, #id_description:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
#id_description {
|
||||||
|
min-height: 120px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.detail-row {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
115
boxes/static/css/thing_detail.css
Normal file
115
boxes/static/css/thing_detail.css
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
.markdown-content p {
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
}
|
||||||
|
.markdown-content p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.markdown-content h1, .markdown-content h2, .markdown-content h3,
|
||||||
|
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
|
||||||
|
margin: 1.5em 0 0.5em 0;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.markdown-content h1:first-child, .markdown-content h2:first-child,
|
||||||
|
.markdown-content h3:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.markdown-content ul, .markdown-content ol {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
|
.markdown-content li {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
.markdown-content code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.markdown-content pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
.markdown-content pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.markdown-content blockquote {
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
background: #f8f9fa;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.markdown-content a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.markdown-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.markdown-content table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
.markdown-content th, .markdown-content td {
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.markdown-content th {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.markdown-content hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid #e0e0e0;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
z-index: 9999;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: zoom-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox img {
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 30px;
|
||||||
|
color: white;
|
||||||
|
font-size: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10000;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
{% load dict_extras %}
|
{% load dict_extras %}
|
||||||
|
|
||||||
@@ -286,29 +287,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<style>
|
<link rel="stylesheet" href="{% static 'css/edit_thing.css' %}">
|
||||||
#id_name, #id_description {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px 15px;
|
|
||||||
border: 2px solid #e0e0e0;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 15px;
|
|
||||||
background: white;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
#id_name:focus, #id_description:focus {
|
|
||||||
border-color: #667eea;
|
|
||||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
#id_description {
|
|
||||||
min-height: 120px;
|
|
||||||
font-family: inherit;
|
|
||||||
}
|
|
||||||
.detail-row {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
{% load thumbnail %}
|
{% load thumbnail %}
|
||||||
{% load dict_extras %}
|
{% load dict_extras %}
|
||||||
|
|
||||||
@@ -169,121 +170,5 @@ $(document).ready(function() {
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<style>
|
<link rel="stylesheet" href="{% static 'css/thing_detail.css' %}">
|
||||||
.markdown-content p {
|
|
||||||
margin: 0 0 1em 0;
|
|
||||||
}
|
|
||||||
.markdown-content p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.markdown-content h1, .markdown-content h2, .markdown-content h3,
|
|
||||||
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
|
|
||||||
margin: 1.5em 0 0.5em 0;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.markdown-content h1:first-child, .markdown-content h2:first-child,
|
|
||||||
.markdown-content h3:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.markdown-content ul, .markdown-content ol {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
.markdown-content li {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
.markdown-content code {
|
|
||||||
background: #f4f4f4;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: 'Consolas', 'Monaco', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.markdown-content pre {
|
|
||||||
background: #f4f4f4;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
.markdown-content pre code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.markdown-content blockquote {
|
|
||||||
border-left: 4px solid #667eea;
|
|
||||||
margin: 1em 0;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
background: #f8f9fa;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
.markdown-content a {
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.markdown-content a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.markdown-content table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
.markdown-content th, .markdown-content td {
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
padding: 8px 12px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.markdown-content th {
|
|
||||||
background: #f8f9fa;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.markdown-content hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 2px solid #e0e0e0;
|
|
||||||
margin: 1.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.9);
|
|
||||||
z-index: 9999;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: zoom-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox.active {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox img {
|
|
||||||
max-width: 90%;
|
|
||||||
max-height: 90%;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-close {
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 30px;
|
|
||||||
color: white;
|
|
||||||
font-size: 40px;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10000;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightbox-close:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from gunicorn.glogging import Logger
|
from gunicorn.glogging import Logger
|
||||||
|
|
||||||
TRUSTED_PROXIES = {
|
|
||||||
ip.strip()
|
|
||||||
for ip in os.environ.get("TRUSTED_PROXIES", "").split(",")
|
|
||||||
if ip.strip()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class HealthCheckFilter(logging.Filter):
|
class HealthCheckFilter(logging.Filter):
|
||||||
def filter(self, record):
|
def filter(self, record):
|
||||||
@@ -17,62 +10,9 @@ class HealthCheckFilter(logging.Filter):
|
|||||||
|
|
||||||
|
|
||||||
class CustomLogger(Logger):
|
class CustomLogger(Logger):
|
||||||
def atoms(self, resp, req, environ, request_time):
|
def setup(self, cfg):
|
||||||
atoms = super().atoms(resp, req, environ, request_time)
|
super().setup(cfg)
|
||||||
atoms["{client-ip}e"] = self._get_client_ip(environ)
|
self.access_log.addFilter(HealthCheckFilter())
|
||||||
return atoms
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_client_ip(environ):
|
|
||||||
remote_addr = environ.get("REMOTE_ADDR", "-")
|
|
||||||
xff = environ.get("HTTP_X_FORWARDED_FOR", "")
|
|
||||||
if not xff:
|
|
||||||
return remote_addr
|
|
||||||
# Walk the chain from right to left, skipping trusted proxies
|
|
||||||
ips = [ip.strip() for ip in xff.split(",")]
|
|
||||||
for ip in reversed(ips):
|
|
||||||
if ip not in TRUSTED_PROXIES:
|
|
||||||
return ip
|
|
||||||
# All IPs in the chain are trusted; fall back to the leftmost
|
|
||||||
return ips[0]
|
|
||||||
|
|
||||||
|
|
||||||
logger_class = CustomLogger
|
logger_class = CustomLogger
|
||||||
access_log_format = '%({client-ip}e)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
|
|
||||||
|
|
||||||
logconfig_dict = {
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": False,
|
|
||||||
"filters": {
|
|
||||||
"health_check": {
|
|
||||||
"()": HealthCheckFilter,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"console": {
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stderr",
|
|
||||||
},
|
|
||||||
"access_console": {
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"filters": ["health_check"],
|
|
||||||
"stream": "ext://sys.stdout",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"level": "INFO",
|
|
||||||
"handlers": ["console"],
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"gunicorn.error": {
|
|
||||||
"level": "INFO",
|
|
||||||
"handlers": ["console"],
|
|
||||||
"propagate": False,
|
|
||||||
},
|
|
||||||
"gunicorn.access": {
|
|
||||||
"level": "INFO",
|
|
||||||
"handlers": ["access_console"],
|
|
||||||
"propagate": False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,388 +7,8 @@
|
|||||||
<title>{% block title %}LabHelper{% endblock %}</title>
|
<title>{% block title %}LabHelper{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||||
<style>
|
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
||||||
* {
|
{% block extra_css %}{% endblock %}
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 15px 30px;
|
|
||||||
margin: 20px auto;
|
|
||||||
max-width: 1200px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand i {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-toggle {
|
|
||||||
display: none;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #555;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-toggle:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a,
|
|
||||||
.navbar-nav form {
|
|
||||||
color: #555;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 15px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a:hover,
|
|
||||||
.navbar-nav button:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a i {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #555;
|
|
||||||
font: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.navbar {
|
|
||||||
padding: 15px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-toggle {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav {
|
|
||||||
display: none;
|
|
||||||
width: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav.active {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a,
|
|
||||||
.navbar-nav form {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a:first-child,
|
|
||||||
.navbar-nav form:first-child {
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav a:last-child,
|
|
||||||
.navbar-nav form:last-child {
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-nav button {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 20px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 15px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: #333;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header .breadcrumb {
|
|
||||||
color: #888;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header .breadcrumb a {
|
|
||||||
color: #667eea;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header .breadcrumb a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: 15px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section h2 {
|
|
||||||
color: #667eea;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 3px solid #667eea;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section h2 i {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: linear-gradient(135deg, #7f8c8d 0%, #95a5a6 100%);
|
|
||||||
box-shadow: 0 4px 15px rgba(127, 140, 141, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover {
|
|
||||||
box-shadow: 0 6px 20px rgba(127, 140, 141, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-sm {
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
background: linear-gradient(135deg, #00b894 0%, #00cec9 100%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
padding: 30px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a {
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
background: white;
|
|
||||||
min-width: 200px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 10px 0;
|
|
||||||
z-index: 1000;
|
|
||||||
top: 100%;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a {
|
|
||||||
display: block;
|
|
||||||
padding: 12px 20px;
|
|
||||||
color: #555;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a:first-child {
|
|
||||||
border-radius: 10px 10px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a:last-child {
|
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content button:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown:hover .dropdown-content,
|
|
||||||
.dropdown:focus-within .dropdown-content {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #667eea;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-btn:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.dropdown-content {
|
|
||||||
position: static;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-content a {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-btn {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{% block extra_css %}{% endblock %}
|
|
||||||
</style>
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -413,12 +33,12 @@
|
|||||||
<a href="/resources/"><i class="fas fa-folder-open"></i> Resources</a>
|
<a href="/resources/"><i class="fas fa-folder-open"></i> Resources</a>
|
||||||
<a href="/fixme/"><i class="fas fa-exclamation-triangle"></i> Fixme</a>
|
<a href="/fixme/"><i class="fas fa-exclamation-triangle"></i> Fixme</a>
|
||||||
<a href="/admin/"><i class="fas fa-cog"></i> Admin</a>
|
<a href="/admin/"><i class="fas fa-cog"></i> Admin</a>
|
||||||
<form method="post" action="{% url 'logout' %}" style="padding: 0; margin: 0; background: none; border: none;">
|
<form method="post" action="{% url 'logout' %}" id="logout-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" style="width: 100%; text-align: left; padding: 12px 20px; background: none; border: none; color: #555; font: inherit; cursor: pointer; display: block; transition: all 0.2s ease;">
|
|
||||||
<i class="fas fa-sign-out-alt"></i> Logout
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
<a href="#" onclick="document.getElementById('logout-form').submit(); return false;">
|
||||||
|
<i class="fas fa-sign-out-alt"></i> Logout
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
Reference in New Issue
Block a user