actualizado 3-sept

This commit is contained in:
2026-09-03 19:40:44 +02:00
parent 89992823b4
commit 5d2912f789
4338 changed files with 350157 additions and 386 deletions
Vendored
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -9,7 +9,9 @@ COPY . .
EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:5000", "app:app"]
# CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
# gunicorn -w 2 -b 0.0.0.0:5000 app:app
# CMD ["python", "app.py"]
# docker compose up -d --build
+1 -1
View File
@@ -1,2 +1,2 @@
APP NEXUS
APP PyFleet
Gestion de flota
BIN
View File
Binary file not shown.
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
## License
Copyright (C) Max Geissler
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
+66
View File
@@ -0,0 +1,66 @@
# adminer-sqlite
With adminer-sqlite you can use [adminer](https://www.adminer.org) with a local [sqlite](https://sqlite.org) database.
## Usage
Hint: The following usage instructions are written for macOS and Linux. On Windows, the commands must be adapted.
1. Download and install [Docker](https://www.docker.com/).
2. Download adminer-sqlite
```
git clone https://github.com/fluidblue/adminer-sqlite.git
cd adminer-sqlite
```
3. Copy your database to `db` directory -or- create a symbolic link to the database
```
# Option 1
cp /path/to/db.sqlite3 ./db/db.sqlite3
```
# Option 2
ln -s /path/to/db.sqlite3 ./db/db.sqlite3
```
4. Set a new password in `docker-compose.yml` (environment variable `ADMINER_PASSWORD`).
Note: You can't use an empty password.
5. Use Docker compose to start the container
```
docker compose up -d
```
6. Go to http://localhost:8080 and enter the following
```
System: SQLite 3
Username: <empty>
Password: <your password>
Database: /db/db.sqlite3
```
Para acceder a nexus:
```
System: SQLite 3
Username: <empty>
Password: p4ssw0rd
Database: /db/pyfleet.db
```
La ruta Database es la ruta dentro del contenedor, no la ruta del host.
## License
Copyright (C) 2023 Max Geissler
This program is free software, licensed under the GNU General Public License (GPL) v3.0 or later.
Please see the [License](LICENSE.md) for further information.
```
+69
View File
@@ -0,0 +1,69 @@
/*
Theme by Hever [http://hev.cz] - September 2019, ver 0.2.5
Used Silk icon set 1.3 by Mark James - http://www.famfamfam.com/lab/icons/silk
*/
.error {background:#FFEEEE url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIsSURBVDjLpVNLSJQBEP7+h6uu62vLVAJDW1KQTMrINQ1vPQzq1GOpa9EppGOHLh0kCEKL7JBEhVCHihAsESyJiE4FWShGRmauu7KYiv6Pma+DGoFrBQ7MzGFmPr5vmDFIYj1mr1WYfrHPovA9VVOqbC7e/1rS9ZlrAVDYHig5WB0oPtBI0TNrUiC5yhP9jeF4X8NPcWfopoY48XT39PjjXeF0vWkZqOjd7LJYrmGasHPCCJbHwhS9/F8M4s8baid764Xi0Ilfp5voorpJfn2wwx/r3l77TwZUvR+qajXVn8PnvocYfXYH6k2ioOaCpaIdf11ivDcayyiMVudsOYqFb60gARJYHG9DbqQFmSVNjaO3K2NpAeK90ZCqtgcrjkP9aUCXp0moetDFEeRXnYCKXhm+uTW0CkBFu4JlxzZkFlbASz4CQGQVBFeEwZm8geyiMuRVntzsL3oXV+YMkvjRsydC1U+lhwZsWXgHb+oWVAEzIwvzyVlk5igsi7DymmHlHsFQR50rjl+981Jy1Fw6Gu0ObTtnU+cgs28AKgDiy+Awpj5OACBAhZ/qh2HOo6i+NeA73jUAML4/qWux8mt6NjW1w599CS9xb0mSEqQBEDAtwqALUmBaG5FV3oYPnTHMjAwetlWksyByaukxQg2wQ9FlccaK/OXA3/uAEUDp3rNIDQ1ctSk6kHh1/jRFoaL4M4snEMeD73gQx4M4PsT1IZ5AfYH68tZY7zv/ApRMY9mnuVMvAAAAAElFTkSuQmCC") no-repeat scroll 0.8em center; padding-left:38px;}
.message, #menu p.message {background:#EEFFEE url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKfSURBVDjLpZPrS1NhHMf9O3bOdmwDCWREIYKEUHsVJBI7mg3FvCxL09290jZj2EyLMnJexkgpLbPUanNOberU5taUMnHZUULMvelCtWF0sW/n7MVMEiN64AsPD8/n83uucQDi/id/DBT4Dolypw/qsz0pTMbj/WHpiDgsdSUyUmeiPt2+V7SrIM+bSss8ySGdR4abQQv6lrui6VxsRonrGCS9VEjSQ9E7CtiqdOZ4UuTqnBHO1X7YXl6Daa4yGq7vWO1D40wVDtj4kWQbn94myPGkCDPdSesczE2sCZShwl8CzcwZ6NiUs6n2nYX99T1cnKqA2EKui6+TwphA5k4yqMayopU5mANV3lNQTBdCMVUA9VQh3GuDMHiVcLCS3J4jSLhCGmKCjBEx0xlshjXYhApfMZRP5CyYD+UkG08+xt+4wLVQZA1tzxthm2tEfD3JxARH7QkbD1ZuozaggdZbxK5kAIsf5qGaKMTY2lAU/rH5HW3PLsEwUYy+YCcERmIjJpDcpzb6l7th9KtQ69fi09ePUej9l7cx2DJbD7UrG3r3afQHOyCo+V3QQzE35pvQvnAZukk5zL5qRL59jsKbPzdheXoBZc4saFhBS6AO7V4zqCpiawuptwQG+UAa7Ct3UT0hh9p9EnXT5Vh6t4C22QaUDh6HwnECOmcO7K+6kW49DKqS2DrEZCtfuI+9GrNHg4fMHVSO5kE7nAPVkAxKBxcOzsajpS4Yh4ohUPPWKTUh3PaQEptIOr6BiJjcZXCwktaAGfrRIpwblqOV3YKdhfXOIvBLeREWpnd8ynsaSJoyESFphwTtfjN6X1jRO2+FxWtCWksqBApeiFIR9K6fiTpPiigDoadqCEag5YUFKl6Yrciw0VOlhOivv/Ff8wtn0KzlebrUYwAAAABJRU5ErkJggg==") no-repeat scroll 0.8em center; padding-left:38px;}
#dbs span, th a[href*="&db="]:not([href*="&select="]):not([href*="&table="]) {background:transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEYSURBVBgZBcHPio5hGAfg6/2+R980k6wmJgsJ5U/ZOAqbSc2GnXOwUg7BESgLUeIQ1GSjLFnMwsKGGg1qxJRmPM97/1zXFAAAAEADdlfZzr26miup2svnelq7d2aYgt3rebl585wN6+K3I1/9fJe7O/uIePP2SypJkiRJ0vMhr55FLCA3zgIAOK9uQ4MS361ZOSX+OrTvkgINSjS/HIvhjxNNFGgQsbSmabohKDNoUGLohsls6BaiQIMSs2FYmnXdUsygQYmumy3Nhi6igwalDEOJEjPKP7CA2aFNK8Bkyy3fdNCg7r9/fW3jgpVJbDmy5+PB2IYp4MXFelQ7izPrhkPHB+P5/PjhD5gCgCenx+VR/dODEwD+A3T7nqbxwf1HAAAAAElFTkSuQmCC") no-repeat scroll left bottom; padding-left:22px;}
.links a {margin-right:8px;}
a[href$="&sql="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHGSURBVHjaxFPNLkNREP5aB6WhaKLSVtKFiIUqK1sLT2DhTXgDL2FlxSOw8FOLRiyQWhDRiKSEhKQJou49P9fMnFsLsevCJCcz594z3/m+mTmJKIrQjSXRpXUNoGqbm39qcMbAkbyIvbVwYQhD3tIeWsOQ1+QVH86Xyz+JXJPIOb9iAI4ZpOMd/yN/vb/vAdiC93cP0El2dNA6z4RjYyW2MaPU0BB0u+0BOGGsVML49LSA3J+cYDCblb0l6jeHBwg/26isrOB0a8uzYBlBgKShgD8M53J4aTTQqFaRSCYl+WxnG83zcxTmKpKg+vtFu9W8NDQDMCXW+VivYyAzjKmlJahUSpiYUKPVbCKTz0sCGzOyOpBLzdcXlCMdDDIxO4vboyoyhQIKlYocLi0uIjczIyBcA98dYqx9NywxUEyDi/P29IyF1VU5dFeroS+dRnF+Xvajk5N0ayjx8tq6+FuSyiwUF4LRHi/reLg4o9ijs6xG9RjWGowUi/h4fcXexoa0L4oc0mNZuVgFzID09VKBnFXoUb7Pnb5zQrvVovpkZC4QzwfiN6QM1eBqdxcmnq6IAA395Mlz8eTxTZwg/pcl/v01fgswAESqYZbsIsnLAAAAAElFTkSuQmCC") no-repeat scroll left bottom; padding-left:22px;}
a[href*="&dump="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJrSURBVHjajFPPaxNREP72V8ymTbGQbRB/IT0otdV6CcWTgqCoUFQQEXoSEQ/+AypK755E1KsULIpGgw1F9KC22lgstFgQLdoWhSab2PRHTNrs7nvObGhSsRa/ZXjv7c6b+eabWUVKiX9AJQuRRci2k+260N91VwgJIQSEV1mVnjf5axTjvCfFFn7hCcBlBzJNVRA0gAZTQ1ODgXC4gDtjV9AW2QNFUTA0/Q66kPLSqVg4shYF8vEdKQ7mln+i+/VVRM0oFpbmETLq/SS6J4R/OfHtHmV24XouHFo94REDAyE9hMZgBMkvT2GZFqxQE6by0/g4P74SoKLB2ZZzWA8ffqRQLBcxNTuFjRTwxpmbfad7Oo/rHhdN6B20/6JvaKyBCnODhqNbL+PRxHXUGXWINV5kl9TDrkQct/pnZKHkyPsDGcl4MJiWq5Ecsf84LxTL8nbyK2+Pke3XHWJAQkLXgMdDNjRNRTxlQ6UmakRD0vN8NEd7EBsFO6Impu1fzGCCrOSXwCro5HEiZiExnEVnrKlaypNhG4fba02aTC8ik1/ibZrM9RlIkkGnjH0jWWJQWRn8TpLI8fcZ6MSA1WrZFERx2eHPTEPqjksBqASDLh7ZZ+HlWA6H9tYyvhjNomNnI8Km7p8/f5+HGdB46/lJeOoYPHV8mbGy8gA59HngU74asNkKYKVzlQCuW9GA6B5si+DVeA4HWmsM4qksTnZY1TMz0NRauysaUAmt2+oxM1tCM6k8mS74WXjIdm8O+JdWg36oKgXqgnzb3TvRXvZEneN6YPNIFw7MY70W5haXnlUHbp3f+b/wW4ABAAtWTLcKdqLcAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
a[href$="&dump="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHzSURBVHjajJPPS+NQEMcnaaxpdWsp6Q8vtWtdkIo9+B948SjIgruCUg8F/wGP/glePe5JcBehwl4qePGm0J6KQj30UBHEzaFLUromTfPizJMnrxhhB4bJvDfzme97SZQgCOB/be98J2AsAMYYMP81KjKg0WhMYljHtS30RSz4gjkGdot559F7OLt8vvi1bJRBURS4vr8CTTQ3m80SFv1MpVJlwzBA13XuNGUwGKyY1p+Vk9aPzWw8C7ZjQXximu+9ATA5KhQK5Uwmw+nCIpEIJBIJOLjah3Q8w7379x5urFsOUEUhTl/C6R+ef35mARzPhW6vC+q/iHvyvaYwXwIQje6j1+tBv98H13VhNBpxxyPA5tw2TI50MPQsrMbXbOo53fmtaJIC7iSZAMPhkOe+73MA5Ruz3yCfz9NlvykLBUSjUR41TQPP8zhE7JNSyj8E0CbJpkiXKRoIRFABeQcQd0DFwlVV5evUTFBZRSiATEwnp4lCAa0LI2AoQC4WimRl4qgyYOw12rYNyWSSX6L4mKiBnukDK5VKYJrmmBpZwW6r1TotFotTuVyOQwQgnU7zV9npdKDdbj+hgnXRN/Yz1ev1zwg6QP+KU2disRiXblkWOI7zhJOPce+wWq2aoQDZarXaJ2xYQGfod5VKxQ2rexFgAI4OiAKxKkWeAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
a[href*="&import="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gEJFDQPlDJUDwAAAiNJREFUOMutk0tI1FEUh7//Y9TJghyc1KQ20lMthRAlgoKgVkWte6wid0EICUHUQjdBErRqHYmQghgU5KLQGB8FKVEMCi0G1JmRbPLJ/O89p8XYOAMZBB24HM5dfL/fOfdcB6Aj1j6kqm2qWv/o5NMk/xBOR6z9dVVpzbmQEyKxPMcBc5svbheHs91YK1hRjJWCY7FGsSKT3VcPtviquqaqVJVVY6xlZu0hYpRrpyJ/Vb7fN3sMwAG4965Hw+ElKrzdlPphPqTHaYw0EdiAQEwuW4MRg7GGzrYu7j6L8/hGg+MD7DNXmF7tpDpSg6MOYpWbzbe2VV/dMATGAuADTLt3UFEmU+OICCpK72hqW8CFExGymwAAnrya15X1QJ+PJFVVtW90QQvjRay4TmfW9XrPpOYdBFYQVXwP+mMpPM9lYCyF44K7KTI4kQJVQj4cqd1BNihowVpBAd9zuNQSZXAizcWWPXmH/WPJonp27ifGCEUOVMB34eXHNJ6Xy5C7+w3xHRDg6N4ylEKAEVSVkAfnm6MMTy1y9nhlXvHNpzSthyrYFfYBiCcylJf6WwAjOZrnOgxPLQLks+tAIDDydSkPrIuWYG2BA2NMbgYunGms5O3nRU43bDkYGEtzuTWar+OJDN5ma1szUKVh/07mv69TVxXm28JK/i/U15YQT2SKdkFEpeAV9P2D3pmmrJXywFgCY7FGCKxgrPnjMv1Y3hjif8QvwdlGGi2XXTgAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:22px;}
a[href$="&import="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gEJFDMETKEbQAAAAjZJREFUOMuVkjFoU2EUhc//3gsvptFCIdRHh2CrQ1AcmsUYHTKJDkJrFUdx6uJkpuLspCI4xaJLNik4SMGlOKgEdekQ0EEwFhJqCw+a16RJ//+/x0Heo6XN4B0v537nHLgKAKqNxXckSyTPPyu//IP/GFVtLL6f9INrKZXC791f3UvDysPP3tryjcHt7yTPkRQRaZL8KSKvRGRtbm7OxgCPZJ8kJtOnYaw99ZUfl8UQ5XK5kMlkQBI7OzuzURTNbmxs3Nne3v60srJyd2FhoQ0AztPLtflOv/22tdtCVo3hwvhFUATZbBaO48B1XUxMTCCfz6NUKmF6evqKtfZxnMABgOdXl+c7vTZ87wQUFcRyZOepqSmIyPWkAgA8+HCfFMG3rS8QEVCIvb09pFIpkES/30cYhmi325iZmYGI4BDgReW1Wl1d3apUKrler4coitBqtUASxhh0u10Mh0N4ngeSRwEAQBIk4bou0uk0PM+D53nQWsMYA/JfLRGBtfYoQEQSx/hIKQVrbbJzHGd0ghgQHxhj4LourLXQWkNrDdd1E81IQCy21iaAOEFcU2t9PEBEEvcYeDBZrNvf38ehPwAAay3CMEQul4Pv+4f/XSkEQYBisYhOpzMywb1Go/GmUCiM5fN5+L4PpRRIIggCRFGEZrOJ9fX1TWPMzQR+0Kler58RkUcicstxnPFMJgNjDMIwxGAw2DTG1K21T5aWlraOBRycWq120hhz1hgjWusf1Wp1eJzuL3uxkTGLHB98AAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
@media all and (min-width: 881px) {
#menu .links {height:22px; transition:.2s;}
#menu .links:hover {height:5em;}
#menu .links a {color:transparent; transition:.2s; display:block; margin-bottom:-1.25em;}
#menu .links > a + a {margin-left:22px;}
#menu .links > a + a + a {margin-left:44px;}
#menu .links > a + a + a + a {margin-left:66px;}
#menu .links:hover a {color:blue; margin:0;}
#menu .links a:hover {color:red;}
}
#menu ul#tables a[href*="&select="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHISURBVDjLpVPNK0RRFP+9D98syMwUspHkm9I0YkFZWBFKkZ0s7a3Ewh+ilChK7FgoZCJFKYlYKB8zk2+Z5t0P577He29kQU7dd+6575zf+d1zztWklPiPmOozt/U4SThjXIoyIQS4AJjSXO0lGGlvcXAm6Vzsz4xUhm0AIeX4QLig+C+ZpxbOG1wGhGYHr1zMUmZGWRgs0ha3PE1nX/8mWmdgWTzLB+DUYbhm9FfZ35IEyrhXA3VXJfPbsV8B9LQUIeUHYJ8ASobag1jcucNgW8g9W4reYSDi2YnnZDoDiwCokDANct6NwTB0LEdj0HRA/wxa2SN25JNBEdWluUhZ366gqmAaGvrCAXKOozccTGPgt8+vn8GYSGcgyTYp3dpBnBg42nbQPRBTo5bTvqYkmxL6AQhNTWQGBXY3B7BxlEBXozcW64dxRKoKUZBju+P06gl5WaaviMJBM3TNDlbypemIZgHYOnlwASsCmW7nHADGnBoQ3c76YmweJ9BR5zFYjsbRHwm4tmJg6PhWA7pCXXk+bu7fURHKweXtq/sWaksz7SC/CCGFrwtyZ3r+rCnFRZ7qr1qc6mLZj4f9OEyPL8lVpbX/PucPv5QPKHB1TdEAAAAASUVORK5CYII=") no-repeat scroll right bottom; display:inline-block; height:16px; margin-right:4px; vertical-align:middle; overflow:hidden; padding-left:16px; width:0;}
#menu ul#tables a.active[href*="&select="] {border-left:2px solid #00f; margin-left:-4px; padding-left:18px;}
.links a[href*="&create="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ6SURBVDjLpZNZSNRRGMV//2XGsjFrMg2z0so2K21xIFpepYUiAsGIICLffI8eWiBBeg3qQV+KwBYKLB8qpHUmrahcKLc0QsxldNSxdPz/79LD1ChBUXTh8sG93POdc75zDa01/7NsgGvPR09rzQmpVZZSCqlAKIWUCqk0QqoZWyKFRir1uvxIbsAGUFqXHQqkpP1L57M3Pm5MMJBKpQHUdF9BKIGQAlcJXOlOVykSdye3leO6MmkGQNyHw+uO/1X3bzGBK+S0B1IqAKqDg3986HeCZPffwvJtoNT7lOZLvUdtAPEDAKBkRzo3QwMUb89InN1uGGD3spdE214xe8MRUnM2MfppNW0Pqy7YAK5UKK2xLbhdP4hlmdxpGMQwwQT8ziNiI534c7cT6WrFazikzF2Eb8HS1IQEDdiWwcHAQmpehTkQSAcgNvSMiYFW5uUUMdV3HW+ywefGNqITJsbUUL75k4FWYJtQ+yaMZcXrk1ANk/33mbdiD7EvlRieETy+FJLkMFcjRRSW3emIAwiF1hqPBfu2LGSWbbA1uZ41SfWkrtxPrPcypsfFiWYzFGzGKTjFV28WEJeIUHETLdOgrmkI1VdHpCdEet5enP4qLK9mKrqMgedv6cyrAP+qxOTiUxAi7oEJi8frELoFoTLpa7nI/HQvscgSRt+0kV1SSW7qYtp7xrBMphm4Mi5h/VIfTcEq1u0oJaknSEdNiMYHET7UvcMpPEN31Ed7zxgASmk1I0g6dK66s8CRak5mVxjnfS05+TsZCw/T9baTx1nnGb47DrQksjE6HrsHYPz6nYt3+Sc3L8+wA2tz0J6pF5OD4WP7Kpq7f5fO79DfSxjdtCtDAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
.links a[href$="&create="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIpSURBVDjLpZNPSFRRFMZ/749/Kt3IqFTSRoSMmrGIYTTbpEJtjBCCok1Em9JVG1dRC8FFEES5aGFEgRRZWq1iLKKxBiNqLDcltQgmHR9hY6LOu+feFm+YGVsZXbh8nHO53/nud8+xjDH8z3IB7r5avGgMZ8XoBq01okFpjYhGtEGJLtmCKINo/XbgVFPUBdDG9PVEq0P/UvnSvdlwQYFoHQIY/3obpRVKFL5W+OIXUVThrL91AN+XihKCwIeTu85sqPryqsJXUvRARAMwkshsiKB7fw25UgKVJwA40V7H/cl5jh+oL+RGk/P0xIqxl11dr8AXjTYG14HRNxkcx+ZhMoNlg52/ND6VAWMoc6F5+2Zy/l9PMIDrWByL1jI+tcDRaN06BaXxbDqLUnq9AqPBteHpuwUcJ0AIcgBXH93h+/wEyyuLrPk5cmv7gNY8gdIYYyhz4PDeWuIpj85IsS2ujQ2zJAk6DkZpqGnixcwYyU+PifUOX7Eh6DoAx7aIpzwA4imPeMrj+bTH+88PaNkZQWwhsrULsXxie9oAzgcESgUe2NAZCeE6AXZGQhwKh/Cyc5RZVXQ39wFwoeMmjXVhgMqiB8awe0cVP36u0Fi/iW9zvwuzkF3+xUz6Nal0gv6uWww+O02lUwGwmv8FM3l55EtLTvQWXwm+EkRpfNEoUZRXHCE5PUFbuJ0nH4cot1wSH14C3LA2Os6x3m2DwDmgGlgChpLX0/1/AIu8MA7WsWBMAAAAAElFTkSuQmCC") no-repeat scroll left bottom; padding-left:22px;}
#content p a[href*="&create="] {padding-left:22px;}
#content p a[href*="&select="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHISURBVDjLpVPNK0RRFP+9D98syMwUspHkm9I0YkFZWBFKkZ0s7a3Ewh+ilChK7FgoZCJFKYlYKB8zk2+Z5t0P577He29kQU7dd+6575zf+d1zztWklPiPmOozt/U4SThjXIoyIQS4AJjSXO0lGGlvcXAm6Vzsz4xUhm0AIeX4QLig+C+ZpxbOG1wGhGYHr1zMUmZGWRgs0ha3PE1nX/8mWmdgWTzLB+DUYbhm9FfZ35IEyrhXA3VXJfPbsV8B9LQUIeUHYJ8ASobag1jcucNgW8g9W4reYSDi2YnnZDoDiwCokDANct6NwTB0LEdj0HRA/wxa2SN25JNBEdWluUhZ366gqmAaGvrCAXKOozccTGPgt8+vn8GYSGcgyTYp3dpBnBg42nbQPRBTo5bTvqYkmxL6AQhNTWQGBXY3B7BxlEBXozcW64dxRKoKUZBju+P06gl5WaaviMJBM3TNDlbypemIZgHYOnlwASsCmW7nHADGnBoQ3c76YmweJ9BR5zFYjsbRHwm4tmJg6PhWA7pCXXk+bu7fURHKweXtq/sWaksz7SC/CCGFrwtyZ3r+rCnFRZ7qr1qc6mLZj4f9OEyPL8lVpbX/PucPv5QPKHB1TdEAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&page="] {background-image:none; padding-left:0;}
#content p a[href$="&database="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIkSURBVDjLpVNNiFJRFP7eU1E0KSLTMpAwYSxyaidDtChm0WYQ3NSutv2s2kwwm2igNgMtooUQEQhhA9GqhSDTQsZZFDbNDBgVg5bSw9J8rzFF33udc+HGg0ladOHj3nPe+b7zc99VbNvG/yy30yiVSl4SnCNcsixrivYEgY7WJu0faX9EKGUyGVNyFFkBkY/T+WkoFEpFIhEEAgH4/X7w916vB8Mw0Gg00G63y+S7mM1mm4LIAYxisbhSr9c5nT1pjUYju1qt2oVC4YnkqbIUMk6Ew+F/9hyNRkFJLuyaATmFoqZp8Pl88Hq98Hg8wtfv99HpdNBsNhGPx0XsRAG3241ut4vBYCDs8XgMXdcxHA7FN/b9VUD25HK5RAUczKC+hYgcNpNN05xcAQdLkqIoIlj6VFWdXIEUkAQGV8M2k2vaG3z6sYGfVR39XzsHlm/dX3h5d31xlwAHM5goBd5+LuO75z3OnU3jyP4EVrZeKGub2p309cP7VKcAQ2Znoiz3deMVTk1Nw1RNTB+ahamMkD45w7RrfwSYwFdFf6K4Quf6pmvwKHswl7wh7Jvnc4gfTPHR52zhcqVSeZZMJgOxWEyI8BC5CmOnh63WKtZbZczPPsa94hX4XCLJQHG+xnw+f5SEFghZmvhefgvcTqn2HN3gBmZSZ5CInMaHr1Wsvivjy3ZvSZn0nHO5XJDIxwgWDbW2vL10m9xXCUGCQXi49qA1/xvyq6BCh7yZeQAAAABJRU5ErkJggg==") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&edit="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg==") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&table="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJcSURBVDjLpZPtT5JhFMafrW997I9rscA+FFu2QRurtlw5cQ4InLpwBogIPNFSiNJ4C+JVkj0QTBHQKFPQlJfwlanY1tXz3ARkn2jd27Wz++yc33XOvd0UAOp/RNGR/X5zeH9rOlTDVKAK3fsqJrxlqN27GHPuYHh+G4rXRQzZNjEws47Hli/oo/PxNsAU3qvWT3/gX3TPuHrWBhiC30nSktXDtKLB1NI4NKkxqBMqjDByPFkcxNBCPwbCfXgUeEBq705m0AZM+qsk2e3hau88W+4ANOy+XPLFQrkrcbW31KkOYJx9rBaAOzPR0gVHW6x593q9cDgcqB6e4sZoogMYdXzD0ck5ZhfLsHGKVfAqVoadKcMdzcLr82PuwwZCoRACgQCWVzdhoK2gaVpDAMNzWzhkAXamQpze/I4t13w+j2AwiFwuh7W1NXg8HmQyGSgUCshkssuU3F7AQf0c84kK3n68KFc4hXQ6DavVCqlUCqVSSdaIx+NQq9UGMsHg7Ab2jxtwp5rOvqUqia3CUqnEObWn0mp1KBaLcLlckMvloPpfrhOAl230/SGLxQK3241CoQC9Xg9nskKk1emQzWZZkBZCoRBU3/NP2GMBgXTTObjSjI1GA8lkEgzDwO/3E4iObXY6nYhEIhCJRHoWcIW6b1pF7egMlYNT7NROUKzU8XX3GJ+3D2E0GgmAm4Zbh2s0mUyIRqMcAGKx+BIlMeSiYu1K/fbEMm4+TaFnJIHrSgZX5TFIZNPo7e1Fj9QOs9kMlUqFaw9pCASCnzwe7x15xG6/rUQiAZ/Px9/5XyhZOMVGKlOdAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
th a[href*="&table="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHISURBVDjLpVPNK0RRFP+9D98syMwUspHkm9I0YkFZWBFKkZ0s7a3Ewh+ilChK7FgoZCJFKYlYKB8zk2+Z5t0P577He29kQU7dd+6575zf+d1zztWklPiPmOozt/U4SThjXIoyIQS4AJjSXO0lGGlvcXAm6Vzsz4xUhm0AIeX4QLig+C+ZpxbOG1wGhGYHr1zMUmZGWRgs0ha3PE1nX/8mWmdgWTzLB+DUYbhm9FfZ35IEyrhXA3VXJfPbsV8B9LQUIeUHYJ8ASobag1jcucNgW8g9W4reYSDi2YnnZDoDiwCokDANct6NwTB0LEdj0HRA/wxa2SN25JNBEdWluUhZ366gqmAaGvrCAXKOozccTGPgt8+vn8GYSGcgyTYp3dpBnBg42nbQPRBTo5bTvqYkmxL6AQhNTWQGBXY3B7BxlEBXozcW64dxRKoKUZBju+P06gl5WaaviMJBM3TNDlbypemIZgHYOnlwASsCmW7nHADGnBoQ3c76YmweJ9BR5zFYjsbRHwm4tmJg6PhWA7pCXXk+bu7fURHKweXtq/sWaksz7SC/CCGFrwtyZ3r+rCnFRZ7qr1qc6mLZj4f9OEyPL8lVpbX/PucPv5QPKHB1TdEAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:22px;}
.links #content a[href*="&database="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKRSURBVDjLhVNLTBNRFD3TTju1FCcBaxuaQEr94ydiZIHGpcFISBOi0YREZWHCVoyRxKUxxq0LXcACQyLsjO6KjSEiDfHDz0S0CLSxlFKd2g7MTDudGd+bMAQCxJucuXfuu+fcO/PeYwzDALVoNMqRuI3guq7rR4g/SEBC/Svxc8T3EUTD4bCGTcZQAUI+RvxLr9d70u/3o6KiAm63G3Qtn89DFEUkk0lks9lRkrvW3t6e2lCgRZFI5F0ikaDtjN1MVVVjYmLCGBoa6qccC7Z1kQafz4f/WSAQAGlyaXOOpQ+SNNUymQxcLhc4joPD4TBzkiRBEASkUimEQiGzdlcBlmWRy+WgKIr5Xi6XUSgUUCwWzTVN+IAzeOOde71orP0eAaOkbrDWf6Cw2+3mBLSYgny3KULXPOUY2BUB/hMd4IOn8XfhMGYjvU+2TECLLRLDMNA0zYw5JYa6Ghke/hyEn9/gZEqo3OuHp7qW3yJgESjoNPSdlb8gWCOCr29BMT0Ip5tBYnIWqlL6o8irzVsEaHcKSqQCen4cweok+FAblNRz2JxlODx1cEkzGWmVbTl7Z/jHhgCF1Z3GYjIKf+U8+ANhQn4Gm6OMUiGI9MhHg5Gl1sbu8UnKNc8B7Ui3ipxEcwvlpVFw6hz2N1xGabkXdqeBYqEOmfefEZWac4e6xz9Z22hbn+BmLBZbi8fjEBdG4NF/QdUDSM88hQ4FawKJR6cxLDZl86qzZdtdoDYwMBAkQg/2LL/ovNLVh++Dd7G0OAau9hTkrKgnnE39GW3f/Z6enpUdBSx7ePu4eq+zi4VNw+TbV0gsxFd5b9X5i4+mpnY63tsErl6okhvrfWzT0SAMR3FMXsnean08Pb/b/fgHqpjCspi90kkAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&schema="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFOSURBVDjLtVK7SgNRED0b9iuM2lr4QK1DQIyk0FZsJAj+gH+ilRZb2NjaRHTLmA9QFKz9huzm7t37Hu+u7IJgQjR6YLjDzOXMmcMERIR5EE5qXA4z4sqACYWEC5wfLQXf/WtMIuDSoL0A7DZDjBj/uYI0l8jzEEJYJMkvCEZM4PqZIxlzpGk+kSCY18TGtGYcx9Tv96dOqBUMBgNyzsFaC621312Ac+59yJFlGRhj5VvVoigKvniglEK32w1mkd3r9ejPPAjOhqdknYX18p1/rzo3pYqTh0OSRkJI5UMgPn4s61sX66SkhtEGcISGsQad5gH2FvehfV5BaIF2cwet5RZyKeu68pe5ubKG7dUNP5AQGltMN57Mosgr5EIiVQmYGvtc1PVicqHY+dXpk8Dg7v22XKFo1ARe9v1bDOlXKKKCs4Sn1xdU1v3vIc2CD3bN4xJjfJWvAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&privileges="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGnSURBVDjLxVM9T8JQFD2lBRQRFPAjEhIwXRgwEUjERRMnZPYXuPoHSJz8BQ7+BCcHJzZHiST4MWC0fsQBJUKCEQnGWihtfa/QBgK6MHiT2/tu3+1559x3y2iahlHMghFtZACuUqmkSdyiiaqqoJJo7HVFUQZi1w+Zcrnc9vl8LAXo7YexHhaNdaFQUDiSsFarFftXClriJz4eTqHJX7DMhDEbiiDlfALP80if1CFKLYjfxCUZR9s8ZcByvadG7w/QLl/guVRC8U2Etnutv5dlGXvr44S+3ZRFjcrgjEQvnFvG2pIfGzYOZ5c3KLCsWZjNZvual0wmOwD0oX/cVnFeXQDHfcDpcuGqtYJaTQKcHYBEImGebrDuY1AvPWImtIhjLoraSwtjHhvmv17Nwnw+/zcD/0QDt+9TUD1TmJ6zQWpImFTfyM64XhiLxdAr17h2E2Bnc9XcEEURzWaTFARM2na7HW63+3eAEul8tVqF1+tFMBiEw+EYmDpBEFAsFhEIBBCJRHQAhgyDQEDCFCgejzO5XE4bNn2Gp1IpJpPJaF05d8y//40/Nlww9OOVWvkAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&event="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJlSURBVDjLpZNbaM9hGMc/v8NsTqF2yMhZKIexmpFyRZIiblwouZDauLYoTVEryg2uXIoIOUU5zJkh4e9QDmtJbWxrbUzz/72/9/m6+P3HLslbT9/3ufm+n/f7Pm8gif9ZMcDxe717JLZ62UQzwxukZnhveBOptyHl8anwZk/3b5pZEwOYtGNDzejSfzm58dTH+b8JvFkpwMizdSCBT8E8OJftkzy4BPIOnONHQzPO+eIhBoM5CCrLwNKslBZM8uDykCbwtgMAl/o/GXhvBYMA2rtAlpGYZSR+UIGKCgCSggGSOHy1Q/0DTifufZUknbr/RZJ0+mHWn3mU9edbMu3qG9DmQ08lKSNw3jCJOIKzjzqJopBzLZ3EEVx40smDr/u4e96QGUXPGpkzYQSJywjCwSsIiKOADUvKiUNYX1tOUQhra8oJg4hZ02cQhhGrqyuyp03tTwbOGzKIQ7j8rIsn3Qd4fEVIIn6+kzAMaH35Fn37wbZD68gnCUl+EbAkI3CpIYmiCNZUlwEwbfIUgiBg1cIyJqbzGFPiWbl8GXUb66mqnkrJ2IvUbq88GEI2dQBRGHDjZTcAbZ8+ERDQnOvm+fszVM1egA89C8avwAeO2nlLAeqRxK7j79TzPa/mXJck6darTG8XdM3uhbry+piGrou5I1pcP17h7wwk5k4aRUfPANMrhtP2pZ8J44bx7nMfff29vGl/SNP1LQA0XdtCa2cO4GdhkPRg78kPVYm3kS71uNTjU8N5I/UpxSWracndZOn8ZVx6dZRhQcz9F3cAjgR/+51rt1c2AXXAaOA7cLTlcHvDL6y6kIpO9lqsAAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href$="&view="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gEJFwohC2f85AAAAsxJREFUOMulk8tr3HUAxD+/1z5+u5smMYlmm6R1rTlEmrY0RKqtWMVWW6qtePBv8NaKUFGUgmBB9CJ48aIH8SKGBhGKKEIaPdgam0gTQ7Bm3U2zTbKPZrP7+32fHkqDx0LnNocZmGHGsdbyIHB5QPj/J8Vi8bDV8m0RtY5qGaeNMVijsdbiOF6cDDNXw47u8/l8/so9jXMvQqlU+lK0m6+LdjMRBAFCCBq1DYRU+L5P944OtIiRxtru/qHvhh7d8/K2QalUutysrx/zHEu5XGZl9TbWDUilsygDSimMbJGyEYVdAwB09g3MPTayb9RfXl7+RLTuHHOtYn5hkYHCCH5nAW0sUmmEVCiliIRisL8HWf8b0apj4qW9CvcL5+bSX6uNjcrD2gkYPTDG7zf+YWsrRinF9I8TWGt5Yvw4QioSgc+rx8e5uThPeWGGRrOt/Np6pa83v4ue3j4AlFBEsUDru+UBxEIihdwue7DwOKl0yPTlS77f0z9kMtmcp7XGdV2ymQRr1Qax1AwfeA6pNHEUEwvNYL4XYwxaa7Kd3QzsLlhnfm6mms11dOU6H8L3fep3WswuFFmrNu7ml4pYaKJwllv2Gk1RRciY8f7nGW7tEd7Zc28WNjcqB0W0heMFpNNJUgkPqRSbm1toY2lnZmj2/cYzB5/khf2naGf+ZerWNyyVVyqOtZbZ63/8EtVWDyV9j0xXby0Z5qJkmBGu5xuAcxOndr924kXHeA6nH3mPj+bO4OPy1beTkQ8wum//U9NXpi7drpRPhBtrXWEYkkikcIMEAGvNFQLTwUs7zwLw1t4JJosXgcnU9pSfPnzkla8//zRZbtRPR7E447ru0cB1soBTjdbTf1Z/4lrtB94d/Z4Pr58k5aYAIud+3jh2Ifhg53D4zpGRZxnuOsRi7VembvxMebH1sXO/dx67EFwE3gBywCbw2dX35fn/ANYEZmpcDQc/AAAAAElFTkSuQmCC") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&refresh="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAI/SURBVDjLjZPbS9NhHMYH+zNidtCSQrqwQtY5y2QtT2QGrTZf13TkoYFlzsWa/tzcoR3cSc2xYUlGJfzAaIRltY0N12H5I+jaOxG8De+evhtdOP1hu3hv3sPzPO/z4SsBIPnfuvG8cbBlWiEVO5OUItA0VS8oxi9EdhXo+6yV3V3UGHRvVXHNfNv6zRfNuBZVoiFcB/3LdnQ8U+Gk+bhPVKB3qUOuf6/muaQR/qwDkZ9BRFdCmMr5EPz6BN7lMYylLGgNNaKqt3K0SKDnQ7us690t3rNsxeyvaUz+8OJpzo/QNzd8WTtcaQ7WlBmPvxhx1V2Pg7oDziIBimwwf3qAGWESkVwQ7owNujk1ztvk+cg4NnAUTT4FrrjqUKHdF9jxBfXr1rgjaSk4OlMcLrnOrJ7latxbL1V2lgvlbG9MtMTrMw1r1PImtfyn1n5q47TlBLf90n5NmalMtUdKZoyQMkLKlIGLjMyYhFpmlz3nGEVmFJlRZNaf7pIaEndM24XIjCOzjX9mm2S2JsqdkMYIqbB1j5C6yWzVk7YRFTsGFu7l+4nveExIA9aMCcOJh6DIoMigyOh+o4UryRWQOtIjaJtoziM1FD0mpE4uZcTc72gBaUyYKEI6khgqINXO3saR7kM8IZUVCRDS0Ucf+xFbCReQhr97MZ51wpWxYnhpCD3zOrT4lTisr+AJqVx0Fiiyr4/vhP4VyyMFIUWNqRrV96vWKXKckBoIqWzXYcoPDrUslDJoopuEVEpIB0sR+AuErIiZ6OqMKAAAAABJRU5ErkJggg==") no-repeat scroll 2px bottom; padding-left:22px;}
#content p a[href*="&sql="] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJ5SURBVHjapFNLaxNRFP4mnZmQZtKYpJ2I8ZE0NSbSWKpgBYNUhIJQUDddCaILwb34C0RcCi60vpa14sZFoSjxUWxTFEubFkxf9kFS+7Jp0yQmM5mH905SF9pSwQt3vnPnnvPNd86cA13Xsds+23XKsdMdQx//s0z/6hi54he3e8/0d3ZuK0FTFGhUJkVVhSbLUAiq5IxSCQrBEkGWOu8Lh38HGrlpWnlXCKhNSbZQo3cEE9FomYAuKZstE2wFa8RR1cpKqK2o4JhxmLVJVAlh5Df6IFj5MgENcHq9EAMBg2RucBDVLpdxVon0iXdvIf8sQHRIsNReht3XjI3ZI8iuPAOrkDyorBq3G6vT08ikUuAsFiN46HkXXL56eI41QV1/BMF+GulvCfCMDFvNXtSIHrC0KDTPhXgc7lAQdQ2thk2XIpewnkzCJvTA4T8PabEbfDWD+ZFxyEUdX94sw6QVCkZlxWAQU+8/YGZgALV+v0HgbWmBNzQMR0M7it8fg+HWwQk2cLkkcvJh5NNFsCVJMgq1ubiE5o4OI5CS8FYrya8b9saLKC48gIlTIG/6sBaLw3PpHrTEMrSXr4kPIaAKFkbjSA0PEVuFlB+DO8Ah2HoB8tITVPE6iplDWOn7jLn0CSQevoDV6TI+zEpUASkkZzaTYrLIrH3C/qMCFEbE4th9OEQexfQBrPYP44d+DuY91eBp+1dGgFVIDb729kKpdJfQtIy2yFNMdN/E5McYzAebUEhOYjZuQXaj5+9W/nOYbl9vLN26doOFScVI9BXmZ6dy9jpnpO1O5dfsRtBxxlk4Xu9mT4Z80DkpVlhZvdp+d3RmpyH7JcAAnHiAVYWMsdkAAAAASUVORK5CYII=") no-repeat scroll 2px bottom; padding-left:24px;}
table tbody input[name*="check"] {display:block; float:left;}
table a[href*="&edit="][href*="&where"] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFUSURBVDjLrZM/SAJxGIZdWwuDlnCplkAEm1zkaIiGFFpyMIwGK5KGoK2lphDKkMDg3LLUSIJsSKhIi+684CokOtTiMizCGuzEU5K3vOEgKvtBDe/2Pc8H3x8NAM1fQlx4H9M3pcOWp6TXWmM8A7j0629v1nraiAVC0IrrwATKIgs5xyG5QiE+Z4iQdoeU2oAsnqCSO1NSTu+D9VhqRLD8nIB8F0Q2MgmJDyipCzjvYJkIfpN2UBLG8MpP4dxvQ3ZzGuyyBQ2H+AnOOCBd9aL6soh81A5hyYSGWyCFvxUcerqI4S+CvYVOFPMHxLAq8I3qdHVY5LbBhJzEsCrwutpRFBlUHy6wO2tEYtWAzLELPN2P03kjfj3luqDycV2F8AgefWbEnVqEHa2IznSD6BdsVDNStB0lfh0FPoQjdx8RrAqGzC0YprSgxzsUMOY2bf37N/6Ud1Vc9yYcH50CAAAAAElFTkSuQmCC") no-repeat scroll right bottom; padding-right:18px;}
table input + a[href*="&edit="][href*="&where"] {width:0; float:left; display:block; height:16px; overflow:hidden; text-decoration:none; padding:0 0 0 18px; background-position:2px bottom;}
table thead a[href*="&modify=1"] {background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gEKDjA70u7s9gAAAMZJREFUOMvtksFpwzAUhr9fcUsWyAQewWDIHt6iLrlriBDIBl4maARPYNH6VtAh2FEuCSQksUV77Qc66Ef63nvw5JyLTNA0DXme03UdVVU9PnDOxWEYJk9d109z733MrqLN/oAkiBEJEAjYfqwJIbzsMLu9vC9XoIgxJ94yEX6+meNOoAXACWPAmEsnqQIBx/CFgEGRo0DEdMHuc81vkPd+soy1lqIoaNsWa+2jYG4PkkYoy/IuHMcx6XPf9xj+yL8gYQ/mOAPpeHRKiIdANAAAAABJRU5ErkJggg==") no-repeat scroll right bottom; }
table thead a[href*="&modify=1"] {width:0; display:inline-block; height:16px; overflow:hidden; text-decoration:none; padding:0 0 0 16px; vertical-align: middle;}
table td:first-child {white-space:nowrap;}
input[name="delete"], input[name="drop"] {background:transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHSSURBVHjapFM5bsJQEB2zSIDFJrHYpEtyAyoKJAp6CrqIkBPkNDlBAKXjBEgUpKOBCyQNijFiEZvZl8z7wsjESYpkpNFfPO/Nmz9j6Xg80n/M9fWi3W7fMOnd4XAo8qogAbvO5xKvL6lU6s0aL1kVMDjP5ye/36+Gw2FyOp3EQFqtVtTr9WixWHT5/JhOp6s2ghP4ORaLyaFQiGazGa3Xa0HgdrvJ6/WSpmk0Go0MjnvIZDLVM0Gr1brm/WskEkkA3O/3abvdQjq5XC6xgoiVka7rNB6PNT6ns9nsu+OkpODxeBLBYJAGgwHt9/uzQ8Vms6Hdbie+KYqC+ASTFrARBMx2HwgEaDKZiHqn0yktl0uxtzrMMAyKx+MCc+4Cs13hwQCC1GQy+W3Lms2mUIUygbEqEBLNun8z8zswVgUfLO0WD4Z6kekn8/l8okNM8GFVUMYDoVWQ6HA4bEAzoyzL1O12kbRsJajwhYZhiUajJEnShWSAQaqqKnU6HahEGysXg9RoNPJ8+cwZZLSKp47m8/k5Kxzg4XBocNxDLper2ka5Xq+LUeatilahJLN1mEJ+ZDHKJthGAKvVauJnYi9ysHIqQee1xOsLg3/+mf5inwIMAJMhb74NwG5wAAAAAElFTkSuQmCC") no-repeat scroll left center; padding:1px 5px 1px 18px; border:0; cursor:pointer; font-size:.9em;}
input[name="delete"]:hover, input[name="drop"]:hover {color:red; background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJdSURBVDjLpZP7S1NhGMf9W7YfogSJboSEUVCY8zJ31trcps6zTI9bLGJpjp1hmkGNxVz4Q6ildtXKXzJNbJRaRmrXoeWx8tJOTWptnrNryre5YCYuI3rh+8vL+/m8PA/PkwIg5X+y5mJWrxfOUBXm91QZM6UluUmthntHqplxUml2lciF6wrmdHriI0Wx3xw2hAediLwZRWRkCPzdDswaSvGqkGCfq8VEUsEyPF1O8Qu3O7A09RbRvjuIttsRbT6HHzebsDjcB4/JgFFlNv9MnkmsEszodIIY7Oaut2OJcSF68Qx8dgv8tmqEL1gQaaARtp5A+N4NzB0lMXxon/uxbI8gIYjB9HytGYuusfiPIQcN71kjgnW6VeFOkgh3XcHLvAwMSDPohOADdYQJdF1FtLMZPmslvhZJk2ahkgRvq4HHUoWHRDqTEDDl2mDkfheiDgt8pw340/EocuClCuFvboQzb0cwIZgki4KhzlaE6w0InipbVzBfqoK/qRH94i0rgokSFeO11iBkp8EdV8cfJo0yD75aE2ZNRvSJ0lZKcBXLaUYmQrCzDT6tDN5SyRqYlWeDLZAg0H4JQ+Jt6M3atNLE10VSwQsN4Z6r0CBwqzXesHmV+BeoyAUri8EyMfi2FowXS5dhd7doo2DVII0V5BAjigP89GEVAtda8b2ehodU4rNaAW+dGfzlFkyo89GTlcrHYCLpKD+V7yeeHNzLjkp24Uu1Ed6G8/F8qjqGRzlbl2H2dzjpMg1KdwsHxOlmJ7GTeZC/nesXbeZ6c9OYnuxUc3fmBuFft/Ff8xMd0s65SXIb/gAAAABJRU5ErkJggg==")}
input[name="logout"] {background:transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJHSURBVDjLlZPNi81hFMc/z7137p1mTCFvNZfGSzLIWNjZKRvFRoqNhRCSYm8xS3+AxRRZ2JAFJWJHSQqTQkbEzYwIM+6Yid/znJfH4prLXShOnb6r8/nWOd8Tcs78bz0/f+KMu50y05nK/wy+uHDylbutqS5extvGcxaWqtoGDA8PZ3dnrs2srQc2Zko41UXLmLdyDW5OfvsUkUgbYGbU63UAQggdmvMzFmzZCgTi7CQmkZwdEaX0JwDgTnGbTCaE0G4zw80omhPI92lcEtkNkdgJCCHwJX7mZvNaB0A14SaYJlwTrpHsTkoFlV1nt2c3x5YYo1/vM9A/gKpxdfwyu/v3teCayKq4JEwT5EB2R6WgYmrs2bYbcUNNUVfEhIfFYy69uci+1fuRX84mkawFSxd/4nVWUopUVIykwlQxRTJBTIDA4Pp1jBZPuNW4wUAPmCqWIn29X1k4f5Ku8g9mpKCkakRLVEs1auVuauVuyqHMo8ejNCe+sWPVTkQKXCMmkeZUmUZjETF1tc6ooly+fgUVw9So1/tRN6YnZji46QghBFKKuAouERNhMlbAHZFE6e7pB+He8MMw+GGI4xtOMf1+lsl3TQ4NHf19BSlaO1DB9BfMHdX0O0iqSgiBbJkjm491hClJbA1LxCURgpPzXwAHhg63necAIi3XngXLcRU0fof8ETMljIyM5LGxMcbHxzvy/6fuXdWgt6+PWncv1e4euqo1ZmabvHs5+jn8yzufO7hiiZmuNpNBM13rbvVSpbrXJE7/BMkHtU9jFIC/AAAAAElFTkSuQmCC") no-repeat scroll right center; padding:1px 22px 1px 5px; border:0; cursor:pointer; font-size:.9em;}
input[name="logout"]:hover {color:red;}
+15
View File
@@ -0,0 +1,15 @@
services:
adminer:
image: adminer:latest
container_name: adminernexus
restart: unless-stopped
ports:
- "5006:8080"
# - 127.0.0.1:8080:8080
environment:
ADMINER_PASSWORD: "p4ssw0rd"
volumes:
- ../database:/db
# - ./db:/db
- ./plugins-enabled:/var/www/html/plugins-enabled
- ./adminer.css:/var/www/html/adminer.css
+4
View File
@@ -0,0 +1,4 @@
<?php
require_once('plugins/login-password-less.php');
return new AdminerLoginPasswordLess(password_hash($_ENV["ADMINER_PASSWORD"], PASSWORD_DEFAULT));
+456 -111
View File
@@ -1,24 +1,34 @@
import sqlite3
import os
import io
from flask import Flask, render_template, session, request, redirect, url_for, g, flash, send_file, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
from functools import wraps
from datetime import datetime, timedelta
import unicodedata
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge
app = Flask(__name__)
app.secret_key = 'clave-secreta-navbar-app'
# Límite máximo de subida: 5 MiB
# app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
DATABASE = 'database/pyfleet.db'
PASSWORD_HASH = generate_password_hash('123')
# Duración de inactividad máxima
SESSION_TIMEOUT = timedelta(hours=4)
UPLOAD_FOLDER = 'uploads/documentos'
ALLOWED_EXTENSIONS = {'pdf'}
# ── Auth ─────────────────────────────────────────────────────
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
@@ -66,10 +76,30 @@ def logout():
session.clear()
return redirect(url_for('login'))
# ── Funcion normalizar ───────────────────────────────────────────
def normalizar(texto):
# if texto is None:
# return ''
texto = str(texto).strip().lower()
# Quitar tildes
texto = unicodedata.normalize('NFD', texto)
texto = ''.join(
c for c in texto
if unicodedata.category(c) != 'Mn'
)
texto = ' '.join(texto.split()) # Convertir varios espacios seguidos en uno
return texto
# ── DB helpers ────────────────────────────────────────────────
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(DATABASE)
g.db.create_function("NORM", 1, normalizar)
g.db.row_factory = sqlite3.Row
return g.db
@@ -80,63 +110,24 @@ def close_db(e=None):
def init_db():
db = sqlite3.connect(DATABASE)
db.create_function("NORM", 1, normalizar)
# db.commit()
# db.close()
# ... tablas existentes ...
# ... tablas existentes ...
db.execute('''
CREATE TABLE IF NOT EXISTS mensajes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texto TEXT NOT NULL,
autor TEXT,
fecha DATETIME DEFAULT (datetime('now','localtime'))
CREATE TABLE IF NOT EXISTS documentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vehiculo_id INTEGER NOT NULL REFERENCES vehiculos(id) ON DELETE CASCADE,
nombre TEXT,
filename TEXT NOT NULL,
fecha DATETIME DEFAULT (datetime('now','localtime'))
)
''')
db.execute('''
CREATE TABLE IF NOT EXISTS vehiculos (
"id" INTEGER,
"m_oficial" TEXT,
"m_civil" TEXT,
"m_propia" TEXT,
"VIN" TEXT,
"poliza" TEXT,
"viat" TEXT,
"legajo" TEXT,
"marca" TEXT,
"modelo" TEXT,
"tipo" TEXT,
"denominacion" TEXT,
"color" TEXT,
"cilindrada" TEXT,
"potencia" TEXT,
"combustible" TEXT,
"neumaticos" TEXT,
"plazas" TEXT,
"situacion" TEXT,
"poblacion" TEXT,
"unidad" TEXT,
"grupo" TEXT,
"int_grupo" TEXT,
"int_juzgado" TEXT,
"int_procedimiento" INTEGER,
"f_destino_situacion" TEXT,
"f_alta" TEXT,
"f_baja" INTEGER,
"km" TEXT,
"km_1" TEXT,
"itv_ultima" TEXT,
"itv" TEXT,
"observaciones" TEXT,
"historial" TEXT,
"notas" TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
)
''')
db.commit()
db.close()
# ── Rutas generales ───────────────────────────────────────────
@app.route('/', methods=['GET', 'POST'])
@login_required
@@ -217,47 +208,59 @@ def vehiculos_list():
if 'incl_bajas' in request.args:
incl_bajas = request.args.get('incl_bajas') == '1'
session['incl_bajas'] = incl_bajas
else:
# Si no viene en la URL, recuperamos el último estado
else: # Si no viene en la URL, recuperamos el último estado
incl_bajas = session.get('incl_bajas', False)
terminos = [t.strip() for t in q.split(',') if t.strip()] if q else []
# Filtro base de bajas
filtro_bajas = '' if incl_bajas else "AND (situacion IS NULL OR UPPER(situacion) != 'BAJA')"
# OPCION 1. BAJAS SOLO MUESTRA BAJAS
if terminos:
condiciones = []
params = []
for t in terminos:
like = f'%{t}%'
condiciones.append('''(
m_oficial LIKE ? OR m_civil LIKE ? OR VIN LIKE ?
OR marca LIKE ? OR modelo LIKE ? OR poblacion LIKE ?
OR unidad LIKE ? OR grupo LIKE ? OR tipo LIKE ?
OR situacion LIKE ? OR int_operacion LIKE ?
)''')
params.extend([like] * 11)
for t in terminos:
t_norm = normalizar(t)
if t_norm == 'lanzadestellos':
condiciones.append('lanzadestellos = 1')
continue
like = f'%{t_norm}%'
condiciones.append('''(
NORM(m_oficial) LIKE ? OR
NORM(m_reservada) LIKE ? OR
NORM(VIN) LIKE ? OR
NORM(marca) LIKE ? OR
NORM(modelo) LIKE ? OR
NORM(poblacion) LIKE ? OR
NORM(unidad) LIKE ? OR
NORM(denominacion) LIKE ? OR
NORM(situacion) LIKE ?
)''')
params.extend([like] * 9)
where = ' AND '.join(condiciones)
rows = db.execute(
f'SELECT id, m_oficial, m_civil, marca, modelo, poblacion, unidad, grupo, tipo, situacion FROM vehiculos WHERE {where} {filtro_bajas} ORDER BY id',
f'''SELECT id, m_oficial, m_reservada, marca, modelo, poblacion, unidad,
denominacion, tipo, situacion, lanzadestellos
FROM vehiculos
WHERE {where} {filtro_bajas}
ORDER BY id''',
params
).fetchall()
else: # PARA QUE LA LISTA SALGA EN BLANCO INICIALMENTE
rows = []
# else:
# rows = db.execute(
# '''SELECT id, m_oficial, m_civil, marca, modelo, poblacion, unidad, tipo, tipo, situacion
# FROM vehiculos
# WHERE situacion IS NULL OR UPPER(situacion) != 'BAJA'
# ORDER BY id'''
# ).fetchall()
rows = []
# else:
# rows = db.execute(
# '''SELECT id, m_oficial, m_reservada, marca, modelo, poblacion, unidad, tipo, tipo, situacion
# FROM vehiculos
# WHERE situacion IS NULL OR UPPER(situacion) != 'BAJA'
# ORDER BY id'''
# ).fetchall()
if solo_filas:
@@ -266,6 +269,7 @@ def vehiculos_list():
@app.route('/vehiculos/nuevo', methods=['GET', 'POST'])
@login_required
def vehiculos_nuevo():
@@ -282,15 +286,14 @@ def vehiculos_nuevo():
cursor = db.execute('''
INSERT INTO vehiculos (
m_oficial, m_civil, m_propia, VIN, poliza, viat, legajo, marca, modelo,
tipo, color, cilindrada, potencia, combustible, neumaticos, plazas, lanzadestellos
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
m_oficial, m_reservada, m_propia, VIN, poliza, viat, legajo, marca, modelo,
tipo, color, combustible, plazas, lanzadestellos
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
''', (
f.get('m_oficial'), f.get('m_civil'), f.get('m_propia'), f.get('VIN'),
f.get('m_oficial'), f.get('m_reservada'), f.get('m_propia'), f.get('VIN'),
f.get('poliza'), f.get('viat'), f.get('legajo') or None,
f.get('marca'), f.get('modelo'), f.get('tipo'), f.get('color'),
f.get('cilindrada'), f.get('potencia') or None, f.get('combustible'),
f.get('neumaticos'), f.get('plazas'), lanzadestellos
f.get('combustible'), f.get('plazas'), lanzadestellos
))
# nuevo_id = cursor.lastrowid
db.commit()
@@ -347,15 +350,15 @@ def vehiculos_editar(id):
db.execute('''
UPDATE vehiculos SET
m_oficial=?, m_civil=?, m_propia=?, VIN=?, poliza=?, viat=?, legajo=?,
marca=?, modelo=?, tipo=?, color=?, cilindrada=?, potencia=?, combustible=?, neumaticos=?, plazas=?, lanzadestellos=?,
m_oficial=?, m_reservada=?, m_propia=?, VIN=?, poliza=?, viat=?, legajo=?,
marca=?, modelo=?, tipo=?, color=?, combustible=?, plazas=?, lanzadestellos=?,
situacion=?, poblacion=?, unidad=?,
int_operacion=?, int_grupo=?, int_juzgado=?, int_procedimiento=?,
km=?, km_1=?, itv_ultima=?, itv=?, notas=?
WHERE id=?
''', (
f.get('m_oficial'), f.get('m_civil'), f.get('m_propia'), f.get('VIN'), f.get('poliza'), f.get('viat') or '', f.get('legajo') or None,
f.get('marca'), f.get('modelo'), f.get('tipo'), f.get('color'), f.get('cilindrada'), f.get('potencia') or None, f.get('combustible'), f.get('neumaticos'), f.get('plazas'), lanzadestellos,
f.get('m_oficial'), f.get('m_reservada'), f.get('m_propia'), f.get('VIN'), f.get('poliza'), f.get('viat') or '', f.get('legajo') or None,
f.get('marca'), f.get('modelo'), f.get('tipo'), f.get('color'), f.get('combustible'), f.get('plazas'), lanzadestellos,
f.get('situacion'), f.get('poblacion'), f.get('unidad'),
f.get('int_operacion'), f.get('int_grupo'), f.get('int_juzgado'), f.get('int_procedimiento'),
f.get('km') or None, f.get('km_1') or None, f.get('itv_ultima') or None, f.get('itv') or None,
@@ -372,7 +375,12 @@ def vehiculos_editar(id):
return redirect(f'/viat?q={q_viat}')
return redirect(url_for('vehiculos_list', q=q) if q else url_for('vehiculos_list'))
matricula = vehiculo['m_oficial'] or vehiculo['m_civil'] or ''
documentos = db.execute(
'SELECT * FROM documentos WHERE vehiculo_id=? ORDER BY fecha DESC',
(id,)
).fetchall()
matricula = vehiculo['m_oficial'] or vehiculo['m_reservada'] or ''
return render_template('vehiculos/form.html',
page='vehiculos',
vehiculo=vehiculo,
@@ -386,7 +394,8 @@ def vehiculos_editar(id):
hasta=hasta,
q_itv=q_itv,
origen=origen,
q_viat=q_viat)
q_viat=q_viat,
documentos=documentos)
@app.route('/vehiculos/<int:id>/eliminar', methods=['POST'])
@@ -523,7 +532,6 @@ def historial_eliminar(vid, hid):
# 6. Si no queda ningún historial, limpiar el vehículo
else:
db.execute('''
UPDATE vehiculos
SET
@@ -563,33 +571,50 @@ def vehiculos_exportar():
filtro_bajas = '' if incl_bajas else "AND (situacion IS NULL OR UPPER(situacion) != 'BAJA')"
terminos = [t.strip() for t in q.split(',') if t.strip()] if q else []
# terminos = [t.strip() for t in q.split(',') if t.strip()] if q else []
# Filtro base de bajas
filtro_bajas = '' if incl_bajas else "AND (situacion IS NULL OR UPPER(situacion) != 'BAJA')"
if terminos:
condiciones = []
params = []
for t in terminos:
like = f'%{t}%'
t_norm = normalizar(t)
if t_norm == 'lanzadestellos':
condiciones.append('lanzadestellos = 1')
continue
like = f'%{t_norm}%'
condiciones.append('''(
m_oficial LIKE ? OR m_civil LIKE ? OR VIN LIKE ?
OR marca LIKE ? OR modelo LIKE ? OR poblacion LIKE ?
OR unidad LIKE ? OR grupo LIKE ? OR tipo LIKE ?
OR situacion LIKE ? OR int_operacion LIKE ?
NORM(m_oficial) LIKE ? OR
NORM(m_reservada) LIKE ? OR
NORM(VIN) LIKE ? OR
NORM(marca) LIKE ? OR
NORM(modelo) LIKE ? OR
NORM(poblacion) LIKE ? OR
NORM(unidad) LIKE ? OR
NORM(denominacion) LIKE ? OR
NORM(situacion) LIKE ?
)''')
params.extend([like] * 9)
params.extend([like] * 11)
where = ' AND '.join(condiciones)
# incluir_bajas = any(t.lower() == 'baja' for t in terminos)
# if not incluir_bajas:
# where = f"(situacion IS NULL OR UPPER(situacion) != 'BAJA') AND ({where})"
rows = db.execute(
f'SELECT m_oficial, m_civil, marca, modelo, poblacion, unidad, grupo, tipo, situacion FROM vehiculos WHERE {where} {filtro_bajas} ORDER BY id',
f'''SELECT id, m_oficial, m_reservada, marca, modelo, poblacion, unidad,
denominacion, tipo, situacion, lanzadestellos
FROM vehiculos
WHERE {where} {filtro_bajas}
ORDER BY id''',
params
).fetchall()
else:
rows = db.execute(
'''SELECT m_oficial, m_civil, marca, modelo, poblacion, unidad, grupo, tipo, situacion
'''SELECT m_oficial, m_reservada, marca, modelo, poblacion, unidad, grupo, tipo, situacion
FROM vehiculos
WHERE situacion IS NULL OR UPPER(situacion) != 'BAJA'
ORDER BY id'''
@@ -638,6 +663,202 @@ def vehiculos_exportar():
# UPLOADS FICHAS TECNICAS PDF
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
# @app.errorhandler(RequestEntityTooLarge)
# def handle_file_too_large(e):
# flash('Error: El archivo pdf no puede superar los 5 MB.', 'error')
# return redirect(request.referrer or '/')
@app.route('/vehiculos/<int:id>/documentos/upload', methods=['POST'])
@login_required
def documentos_upload(id):
q = request.form.get('_q', '')
incl_bajas = request.form.get('_incl_bajas', '0')
desde = request.form.get('_desde', '')
hasta = request.form.get('_hasta', '')
q_itv = request.form.get('_q_itv', '')
origen = request.form.get('_origen', '')
q_viat = request.form.get('_q_viat', '')
nombre = request.form.get('nombre_doc', '').strip()
if 'pdf' not in request.files:
flash('No se seleccionó ningún archivo.', 'error')
return redirect(f'/vehiculos/{id}/editar')
file = request.files['pdf']
if file.filename == '' or not allowed_file(file.filename):
flash('Solo se permiten archivos PDF.', 'error')
return redirect(f'/vehiculos/{id}/editar')
# Comprobar tamaño máximo: 5 MiB
MAX_FILE_SIZE = 5 * 1024 * 1024
file.seek(0, os.SEEK_END)
file_size = file.tell()
file.seek(0)
if file_size > MAX_FILE_SIZE:
flash('El archivo PDF no puede superar los 5 MB.', 'error')
return redirect(f'/vehiculos/{id}/editar?q={q}')
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# Nombre único: vehiculo_id_timestamp.pdf
import time
filename = f'v{id}_{int(time.time())}.pdf'
file.save(os.path.join(UPLOAD_FOLDER, filename))
# Si no puso nombre, usar el nombre original del archivo
if not nombre:
nombre = secure_filename(file.filename)
db = get_db()
db.execute(
'INSERT INTO documentos (vehiculo_id, nombre, filename) VALUES (?,?,?)',
(id, nombre, filename)
)
db.commit()
flash(f'Documento «{nombre}» subido correctamente.', 'ok')
url = f'/vehiculos/{id}/editar?q={q}'
if origen == 'viat':
url = f'/vehiculos/{id}/editar?origen=viat&q_viat={q_viat}'
elif desde or hasta or q_itv:
url = f'/vehiculos/{id}/editar?desde={desde}&hasta={hasta}&q_itv={q_itv}'
return redirect(url)
@app.route('/vehiculos/documentos/<int:doc_id>/ver')
@login_required
def documentos_ver(doc_id):
from flask import send_from_directory
db = get_db()
doc = db.execute('SELECT * FROM documentos WHERE id=?', (doc_id,)).fetchone()
if not doc:
flash('Documento no encontrado.', 'error')
return redirect(url_for('vehiculos_list'))
return send_from_directory(UPLOAD_FOLDER, doc['filename'], as_attachment=False)
@app.route('/vehiculos/documentos/<int:doc_id>/descargar')
@login_required
def documentos_descargar(doc_id):
from flask import send_from_directory
db = get_db()
doc = db.execute('SELECT * FROM documentos WHERE id=?', (doc_id,)).fetchone()
if not doc:
flash('Documento no encontrado.', 'error')
return redirect(url_for('vehiculos_list'))
return send_from_directory(UPLOAD_FOLDER, doc['filename'],
as_attachment=True,
download_name=doc['nombre'])
# download_name=doc['nombre'] + '.pdf')
@app.route('/vehiculos/documentos/<int:doc_id>/eliminar', methods=['POST'])
@login_required
def documentos_eliminar(doc_id):
db = get_db()
vid = request.form.get('vid', '')
q = request.form.get('_q', '')
incl_bajas = request.form.get('_incl_bajas', '0')
doc = db.execute('SELECT * FROM documentos WHERE id=?', (doc_id,)).fetchone()
if doc:
filepath = os.path.join(UPLOAD_FOLDER, doc['filename'])
if os.path.exists(filepath):
os.remove(filepath)
db.execute('DELETE FROM documentos WHERE id=?', (doc_id,))
db.commit()
flash('Documento eliminado.', 'ok')
return redirect(f'/vehiculos/{vid}/editar?q={q}&incl_bajas={incl_bajas}')
# @app.route('/vehiculos/<int:id>/upload', methods=['POST'])
# @login_required
# def vehiculos_upload(id):
# q = request.form.get('_q', '')
# incl_bajas = request.form.get('_incl_bajas', '0')
# desde = request.form.get('_desde', '')
# hasta = request.form.get('_hasta', '')
# q_itv = request.form.get('_q_itv', '')
# origen = request.form.get('_origen', '')
# q_viat = request.form.get('_q_viat', '')
# if 'pdf' not in request.files:
# flash('No se seleccionó ningún archivo.', 'error')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# file = request.files['pdf']
# if file.filename == '':
# flash('No se seleccionó ningún archivo.', 'error')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# if file and allowed_file(file.filename):
# # Guardar con nombre único basado en id del vehículo
# os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# filename = f'ficha_tecnica_id_{id}.pdf'
# filepath = os.path.join(UPLOAD_FOLDER, filename)
# file.save(filepath)
# # Guardar referencia en BD
# db = get_db()
# db.execute('UPDATE vehiculos SET pdf=? WHERE id=?', (filename, id))
# db.commit()
# flash('Documento PDF subido correctamente.', 'ok')
# else:
# flash('Solo se permiten archivos PDF.', 'error')
# url = f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}'
# if origen == 'viat':
# url = f'/vehiculos/{id}/editar?origen=viat&q_viat={q_viat}'
# elif desde or hasta or q_itv:
# url = f'/vehiculos/{id}/editar?desde={desde}&hasta={hasta}&q_itv={q_itv}'
# return redirect(url)
# @app.route('/vehiculos/<int:id>/pdf')
# @login_required
# def vehiculos_pdf(id):
# from flask import send_from_directory
# db = get_db()
# vehiculo = db.execute('SELECT pdf FROM vehiculos WHERE id=?', (id,)).fetchone()
# if not vehiculo or not vehiculo['pdf']:
# flash('No hay documento PDF para este vehículo.', 'error')
# return redirect(f'/vehiculos/{id}/editar')
# return send_from_directory(UPLOAD_FOLDER, vehiculo['pdf'], as_attachment=False)
# @app.route('/vehiculos/<int:id>/pdf/eliminar', methods=['POST'])
# @login_required
# def vehiculos_pdf_eliminar(id):
# q = request.form.get('_q', '')
# incl_bajas = request.form.get('_incl_bajas', '0')
# db = get_db()
# vehiculo = db.execute('SELECT pdf FROM vehiculos WHERE id=?', (id,)).fetchone()
# if vehiculo and vehiculo['pdf']:
# filepath = os.path.join(UPLOAD_FOLDER, vehiculo['pdf'])
# if os.path.exists(filepath):
# os.remove(filepath)
# db.execute('UPDATE vehiculos SET pdf=NULL WHERE id=?', (id,))
# db.commit()
# flash('Documento PDF eliminado.', 'ok')
# return redirect(f'/vehiculos/{id}/editar?q={q}&incl_bajas={incl_bajas}')
# Ruta API que devuelve unidades según población
@app.route('/api/unidades/<int:poblacion_id>')
@@ -683,7 +904,7 @@ def itv_list():
params.extend([like] * 3)
where_q = ' AND '.join(condiciones)
rows = db.execute(f'''
SELECT id, m_oficial, m_civil, marca, modelo, tipo,
SELECT id, m_oficial, m_reservada, marca, modelo, tipo,
tipo, unidad, poblacion, itv
FROM vehiculos
WHERE itv IS NOT NULL AND itv != ''
@@ -693,7 +914,7 @@ def itv_list():
''', params).fetchall()
else:
rows = db.execute('''
SELECT id, m_oficial, m_civil, marca, modelo, tipo,
SELECT id, m_oficial, m_reservada, marca, modelo, tipo,
tipo, unidad, poblacion, itv
FROM vehiculos
WHERE itv IS NOT NULL AND itv != ''
@@ -735,7 +956,7 @@ def itv_exportar():
params.extend([like] * 3)
where_q = ' AND '.join(condiciones)
rows = db.execute(f'''
SELECT m_oficial, m_civil, marca, modelo, tipo,
SELECT m_oficial, m_reservada, marca, modelo, tipo,
tipo, unidad, poblacion, itv
FROM vehiculos
WHERE itv IS NOT NULL AND itv != ''
@@ -745,7 +966,7 @@ def itv_exportar():
''', params).fetchall()
else:
rows = db.execute('''
SELECT m_oficial, m_civil, marca, modelo, tipo,
SELECT m_oficial, m_reservada, marca, modelo, tipo,
tipo, unidad, poblacion, itv
FROM vehiculos
WHERE itv IS NOT NULL AND itv != ''
@@ -941,6 +1162,104 @@ def editar_unidades():
# return redirect(url_for('unidades_list'))
############### API PARA USAR TABULATOR ######################################
# @app.route("/api/unidades/<int:unidad_id>", methods=["PATCH"])
# @login_required
# def actualizar_unidad(unidad_id):
# datos = request.get_json(silent=True) or {}
# campo = datos.get("campo")
# valor = datos.get("valor")
# # Solo permitimos estos campos
# if campo not in ("poblacion_id", "unidad"):
# return jsonify({
# "error": "Campo no permitido"
# }), 400
# db = get_db()
# # Comprobar que existe la unidad
# unidad_actual = db.execute(
# "SELECT id FROM unidades WHERE id=?",
# (unidad_id,)
# ).fetchone()
# if unidad_actual is None:
# return jsonify({
# "error": "La unidad no existe"
# }), 404
# # CAMBIO DE POBLACIÓN
# if campo == "poblacion_id":
# try:
# poblacion_id = int(valor)
# except (ValueError, TypeError):
# return jsonify({
# "error": "Población no válida"
# }), 400
# poblacion = db.execute(
# """ SELECT id, poblacion FROM poblaciones WHERE id=? """,
# (poblacion_id,)
# ).fetchone()
# if poblacion is None:
# return jsonify({
# "error": "La población no existe"
# }), 400
# valor = poblacion_id
# # CAMBIO DE UNIDAD
# elif campo == "unidad":
# valor = str(valor or "").strip()
# if not valor:
# return jsonify({
# "error": "El nombre de la unidad no puede estar vacío"
# }), 400
# # UPDATE
# db.execute(f""" UPDATE unidades SET {campo}=? WHERE id=?""",
# (valor, unidad_id)
# )
# db.commit()
# # DEVOLVER EL REGISTRO ACTUALIZADO
# fila = db.execute(
# """
# SELECT
# u.id,
# u.poblacion_id,
# u.unidad,
# p.poblacion
# FROM unidades u
# LEFT JOIN poblaciones p
# ON u.poblacion_id = p.id
# WHERE u.id=? """,
# (unidad_id,)
# ).fetchone()
# return jsonify({
# "id": fila["id"],
# "poblacion_id": fila["poblacion_id"],
# "poblacion": fila["poblacion"],
# "unidad": fila["unidad"]
# })
# ── CRUD tipo ─────────────────────────────────────────
@app.route('/tipo')
@login_required
@@ -1023,7 +1342,7 @@ def viat_list():
terminos = [t.strip() for t in q.split(',') if t.strip()] if q else []
sql = '''
SELECT id, viat, m_oficial, m_civil, marca, modelo,
SELECT id, viat, m_oficial, m_reservada, marca, modelo,
poblacion, unidad, tipo
FROM vehiculos
WHERE viat IS NOT NULL
@@ -1040,7 +1359,7 @@ def viat_list():
(
viat LIKE ?
OR m_oficial LIKE ?
OR m_civil LIKE ?
OR m_reservada LIKE ?
OR marca LIKE ?
OR modelo LIKE ?
OR poblacion LIKE ?
@@ -1076,7 +1395,7 @@ def viat_exportar():
terminos = [t.strip() for t in q.split(',') if t.strip()] if q else []
sql = '''
SELECT id, viat, m_oficial, m_civil, marca, modelo,
SELECT id, viat, m_oficial, m_reservada, marca, modelo,
poblacion, unidad, tipo
FROM vehiculos
WHERE viat IS NOT NULL
@@ -1093,7 +1412,7 @@ def viat_exportar():
(
viat LIKE ?
OR m_oficial LIKE ?
OR m_civil LIKE ?
OR m_reservada LIKE ?
OR marca LIKE ?
OR modelo LIKE ?
OR poblacion LIKE ?
@@ -1126,7 +1445,7 @@ def viat_exportar():
ws.row_dimensions[1].height = 22
for row in rows:
ws.append([row['viat'], row['m_oficial'], row['m_civil'],
ws.append([row['viat'], row['m_oficial'], row['m_reservada'],
row['marca'], row['modelo'], row['poblacion'],
row['unidad'], row['tipo']])
@@ -1285,9 +1604,35 @@ def fecha_es(valor):
# ------------------------------------------------------------
if __name__ == '__main__':
init_db()
app.run(host="0.0.0.0", port=5000, debug=True)
# app.run(debug=True)
# CODIGO PREVIO A INCLUIR NORMALIZACION
# if terminos:
# condiciones = []
# params = []
# for t in terminos:
# like = f'%{t}%'
# condiciones.append('''(
# m_oficial LIKE ? OR m_reservada LIKE ? OR VIN LIKE ?
# OR marca LIKE ? OR modelo LIKE ? OR poblacion LIKE ?
# OR unidad LIKE ? OR grupo LIKE ? OR tipo LIKE ?
# OR situacion LIKE ? OR int_operacion LIKE ?
# )''')
# params.extend([like] * 11)
# where = ' AND '.join(condiciones)
# rows = db.execute(
# f'SELECT id, m_oficial, m_reservada, marca, modelo, poblacion, unidad, grupo, tipo, situacion FROM vehiculos WHERE {where} {filtro_bajas} ORDER BY id',
# params
# ).fetchall()
Binary file not shown.
+77 -6
View File
@@ -11,15 +11,46 @@ navbar_vertical_CRUD/
└── form.html
Adminer acceso
http://10.76.243.137:5001/
System: SQLite 3
Username: <empty>
Password: p4ssw0rd
Database: /db/pyfleet.db
pip install -r requirements.txt
python app.py
# → http://127.0.0.1:5000
gunicorn -w 2 -b 0.0.0.0:5000 app:app
Ejecutar con Python en segundo plano
nohup python app.py > flask.log 2>&1 &
Comprobar que esta activo
ps aux | grep python
Para detenerlo
pkill -f "Python app.py"
Gunicorn en segundo plano.
gunicorn -w 4 -b 0.0.0.0:5000 app:app > gunicorn.log 2>&1 &
nohup gunicorn -w 4 -b 127.0.0.1:5000 app:app > gunicorn.log 2>&1 &
Parar
ps aux | grep gunicorn (averiguar proceso)
pkill -f gunicorn. (nombre proceso)
kill 12345. (Numero proceso)
PRODUCCION LINUX GUNICORN
pip install gunicorn
gunicorn app:app
gunicorn --bind 0.0.0.0:8000 app:app
gunicorn -w 2 -b 0.0.0.0:5000 app:app
gunicorn --bind 0.0.0.0:5000 app:app
gunicorn -w 4 -b 127.0.0.1:8000 app:app
gunicorn --workers 4 --bind 0.0.0.0:8000 app:app
gunicorn --workers 4 --bind 0.0.0.0:8000 --timeout 120 app:app
@@ -36,6 +67,7 @@ kill 1337144 la primera de la lista
pkill -f "gunicorn.*app:app lo elimina de una sola vez
---------------------------------------------------------------
TABLAS
@@ -43,7 +75,7 @@ TABLAS
CREATE TABLE "vehiculos" (
"id" INTEGER,
"m_oficial" TEXT,
"m_civil" TEXT,
"m_reservada" TEXT,
"m_propia" TEXT,
"VIN" TEXT,
"poliza" TEXT,
@@ -54,10 +86,7 @@ CREATE TABLE "vehiculos" (
"tipo" TEXT,
"denominacion" TEXT,
"color" REAL,
"cilindrada" TEXT,
"potencia" TEXT,
"combustible" TEXT,
"neumaticos" TEXT,
"plazas" TEXT,
"lanzadestellos" INTEGER,
"situacion" TEXT,
@@ -77,11 +106,53 @@ CREATE TABLE "vehiculos" (
"itv" INTEGER,
"observaciones" TEXT,
"historial" TEXT,
"notas" TEXT,
"notas" TEXT, pdf TEXT,
PRIMARY KEY("id" AUTOINCREMENT)
)
CREATE TABLE documentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vehiculo_id INTEGER NOT NULL REFERENCES vehiculos(id) ON DELETE CASCADE,
nombre TEXT,
filename TEXT NOT NULL,
fecha DATETIME DEFAULT (datetime('now','localtime'))
)
CREATE TABLE "historial" (
"id" INTEGER,
"vehiculo_id" INTEGER,
"fecha" TEXT,
"matricula" TEXT,
"situacion" TEXT,
"poblacion" TEXT,
"unidad" TEXT,
"grupo" TEXT,
"observaciones" TEXT,
PRIMARY KEY("id" AUTOINCREMENT),
FOREIGN KEY("vehiculo_id") REFERENCES "vehiculos"("id")
)
CREATE TABLE mensajes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
texto TEXT NOT NULL,
autor TEXT,
fecha DATETIME DEFAULT (datetime('now','localtime'))
)
-----------------------------------------
Cambiar nombre de columna
ALTER TABLE nombre_tabla
RENAME COLUMN nombre_actual TO nombre_nuevo;
Eliminar columna (hay que hacerlo de una en una (SQLite no permite varias a la vez)
ALTER TABLE usuarios
DROP COLUMN nombre;
Cambiar None por Null table vehiculos
UPDATE vehiculos
Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

After

Width:  |  Height:  |  Size: 476 B

Before

Width:  |  Height:  |  Size: 507 B

After

Width:  |  Height:  |  Size: 507 B

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

Before

Width:  |  Height:  |  Size: 579 B

After

Width:  |  Height:  |  Size: 579 B

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 250 B

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 279 B

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 286 B

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 854 B

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 457 B

Before

Width:  |  Height:  |  Size: 477 B

After

Width:  |  Height:  |  Size: 477 B

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 564 B

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 607 B

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 634 B

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

Before

Width:  |  Height:  |  Size: 493 B

After

Width:  |  Height:  |  Size: 493 B

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 495 B

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

Before

Width:  |  Height:  |  Size: 521 B

After

Width:  |  Height:  |  Size: 521 B

Before

Width:  |  Height:  |  Size: 601 B

After

Width:  |  Height:  |  Size: 601 B

Before

Width:  |  Height:  |  Size: 617 B

After

Width:  |  Height:  |  Size: 617 B

Before

Width:  |  Height:  |  Size: 640 B

After

Width:  |  Height:  |  Size: 640 B

Before

Width:  |  Height:  |  Size: 662 B

After

Width:  |  Height:  |  Size: 662 B

Before

Width:  |  Height:  |  Size: 727 B

After

Width:  |  Height:  |  Size: 727 B

Before

Width:  |  Height:  |  Size: 251 B

After

Width:  |  Height:  |  Size: 251 B

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 279 B

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 286 B

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 686 B

Before

Width:  |  Height:  |  Size: 717 B

After

Width:  |  Height:  |  Size: 717 B

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 737 B

Before

Width:  |  Height:  |  Size: 804 B

After

Width:  |  Height:  |  Size: 804 B

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 574 B

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 620 B

Before

Width:  |  Height:  |  Size: 684 B

After

Width:  |  Height:  |  Size: 684 B

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 366 B

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 687 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 492 B

After

Width:  |  Height:  |  Size: 492 B

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 840 B

Before

Width:  |  Height:  |  Size: 615 B

After

Width:  |  Height:  |  Size: 615 B

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 315 B

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 315 B

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

Before

Width:  |  Height:  |  Size: 839 B

After

Width:  |  Height:  |  Size: 839 B

Before

Width:  |  Height:  |  Size: 845 B

After

Width:  |  Height:  |  Size: 845 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 324 B

After

Width:  |  Height:  |  Size: 324 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 432 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 311 B

After

Width:  |  Height:  |  Size: 311 B

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 278 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

Before

Width:  |  Height:  |  Size: 394 B

After

Width:  |  Height:  |  Size: 394 B

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 345 B

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 373 B

Before

Width:  |  Height:  |  Size: 374 B

After

Width:  |  Height:  |  Size: 374 B

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

Before

Width:  |  Height:  |  Size: 356 B

After

Width:  |  Height:  |  Size: 356 B

Before

Width:  |  Height:  |  Size: 316 B

After

Width:  |  Height:  |  Size: 316 B

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 364 B

Before

Width:  |  Height:  |  Size: 313 B

After

Width:  |  Height:  |  Size: 313 B

Before

Width:  |  Height:  |  Size: 363 B

After

Width:  |  Height:  |  Size: 363 B

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 435 B

Some files were not shown because too many files have changed in this diff Show More