mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-06-03 05:35:02 -04:00
feat: docker volume validation (#1125)
* feat: add api endpoints for volume check * feat: add docker icon * add size prop * feat: add frontend UI for checking docker-volume * update caddy to server validation file * add more extensive documentation around setup req * fix: wrong type on user id #1123 * spelling * refactor: cleanup excessive function calls
This commit is contained in:
parent
ea141832c3
commit
e9bb39c744
294
docs/docs/assets/img/docker-diagram.drawio.svg
Normal file
294
docs/docs/assets/img/docker-diagram.drawio.svg
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800px" height="377px" viewBox="-0.5 -0.5 800 377" content="<mxfile><diagram id="3dpkjlI-_KjnuoPEQ-Vt" name="Page-1">5VlNU9swEP01maGHeiwp/jpCAuXQTpmhM22PwhaJB8fyKApJ+uu7jqVYihxIMRAoPiTW6sPye29XK3lARrPVF0Gr6TeesWKA/Ww1IOMBxsiPMPzVlrWywNVYJiLPlK01XOd/mO6qrIs8Y3OroeS8kHllG1NeliyVlo0KwZd2s1te2E+t6IQ5huuUFq71Z57JaWONA7+1X7J8MtVPRr6qmVHdWBnmU5rxpWEi5wMyEpzL5m62GrGiRk/j0vS72FO7nZhgpTykAw6aHve0WKiXUxOTa/22gi/KjNUd/AE5W05zya4rmta1SyAYbFM5K6CE4FYNx4Rkq71zQts3BY0wPmNSrKGJ6oDDpodWx7ApLlukoyRpbFMDZRKpflSxO9mO3AIANwqDbjzIi8DhvHsHQvvhiCMvDiI0xM1vYqEzjD2jLg5iB6wAR57RQDufBR1GntECDYf9kRw6SI55eseEgyfAIG3Q5lLwOzbiBRdgKXkJLc9u86LYMdEin5RQTAFcGJic1aDm4KanqmKWZ1n9mE6WbB5veSmv1aT2KfafWbOFrH3eFLLv4Q4p45rU/hS4vj1jgBn7TKv8AVmjV5F1GGKvQ7gKq9i3KrVoDegQgGS6hQbbxDEcerjDdfpgilxQL3/8uHLgZBmsGKrIihu+PG8NByE55wuRqsGUFiQVEyYtbuvHPIi2YAWV+b29cPUBINonqlsBLsTK7OjKGiL/gYD5ZpUV7wM2o5I6oELiUNW36brIAV1BHof2puHh683WQNO7yYad7wsJwzAdf5uEC6T+PJ4eh10LkI6K+NEVLO5YshAKOnns5dsuBdCFVhX8dpLwrE4euE4eH8vJkevlO0hcPBkMwECsf6lld1P4XRe8QBfHK7NyvDZLV0zk8Db1ar8xPjV8Pjuym66nQtC10aDieSnnxshXtcGIVFFghRFCdhIG3BWIWg6b57WMbid+GMm+Q/II4k09wSOnvEFg4ZCEXhQnxuVEiMTfJkx2XuuhZ0hlEXl1sR+ka71rNoUdHS1kaJZbNSUJBDVySny9/T1iUgCzsV3NUth2g26mAcjeN+nMz0rUdxYh3aYXkO7OqVeG+bIS9F0JaiUcQYOJA90J+uQg93/uOAm2NR11p6g7u8+OFlYE7U7O+pCE3WXnBH8UkoIE2yct74MyN7afkA9DGSYeMYO8naMFxPPNi7wVzvAH5iwivheaiNqrfRA+tuU8Emfu8fMIkKGwNRfvh7leh/+bwxv73HQPPS93btpxdv3RaGhOZ1+VBii2372a7Wz7+ZCc/wU=</diagram></mxfile>">
|
||||||
|
<defs/>
|
||||||
|
<g>
|
||||||
|
<rect x="0" y="0" width="799" height="376" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<rect x="261.86" y="34.57" width="527.14" height="321.43" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<rect x="261.86" y="6" width="70.29" height="28.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 20px; margin-left: 263px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
Docker
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="297" y="24" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
Docker
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="636.71" y="66.71" width="128.86" height="64.29" rx="9.64" ry="9.64" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 127px; height: 1px; padding-top: 99px; margin-left: 638px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||||
|
mealie-api
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="701" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
mealie-api
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 513.71 98.86 L 565.33 98.86 Q 575.33 98.86 585.33 98.86 L 630.35 98.86" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 635.6 98.86 L 628.6 102.36 L 630.35 98.86 L 628.6 95.36 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 99px; margin-left: 576px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||||
|
HTTP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="576" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
HTTP
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="384.86" y="66.71" width="128.86" height="64.29" rx="9.64" ry="9.64" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 127px; height: 1px; padding-top: 99px; margin-left: 386px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||||
|
mealie-frontend
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="449" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
mealie-frontend
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 660.14 229.57 C 660.14 221.29 678.5 214.57 701.14 214.57 C 712.02 214.57 722.45 216.15 730.13 218.96 C 737.82 221.78 742.14 225.59 742.14 229.57 L 742.14 315.29 C 742.14 323.57 723.79 330.29 701.14 330.29 C 678.5 330.29 660.14 323.57 660.14 315.29 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<path d="M 742.14 229.57 C 742.14 237.86 723.79 244.57 701.14 244.57 C 678.5 244.57 660.14 237.86 660.14 229.57" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 80px; height: 1px; padding-top: 285px; margin-left: 661px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||||
|
mealie-data
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="701" y="289" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
mealie-data
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 701.05 131 L 701.05 162.79 Q 701.05 172.79 701.05 182.79 L 701.05 208.2" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 701.05 213.45 L 697.55 206.45 L 701.05 208.2 L 704.55 206.45 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 173px; margin-left: 701px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||||
|
/app/data
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="701" y="176" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
/app/data
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 449.29 131 L 449.29 262.43 Q 449.29 272.43 459.29 272.43 L 653.77 272.43" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 659.02 272.43 L 652.02 275.93 L 653.77 272.43 L 652.02 268.93 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 272px; margin-left: 484px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||||
|
/app/data/
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="484" y="276" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
/app/data/
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="29" y="82.79" width="90.57" height="32.14" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 89px; height: 1px; padding-top: 99px; margin-left: 30px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||||
|
Client
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="74" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
Client
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 320.43 98.86 L 342.48 98.86 Q 352.48 98.86 362.48 98.86 L 378.49 98.86" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 383.74 98.86 L 376.74 102.36 L 378.49 98.86 L 376.74 95.36 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<rect x="203.29" y="86" width="117.14" height="25.71" rx="3.86" ry="3.86" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 115px; height: 1px; padding-top: 99px; margin-left: 204px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||||
|
9925:3000
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="262" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||||
|
9925:3000
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<path d="M 119.57 98.86 L 151.05 98.86 Q 161.05 98.86 171.05 98.86 L 196.92 98.86" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||||
|
<path d="M 202.17 98.86 L 195.17 102.36 L 196.92 98.86 L 195.17 95.36 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 99px; margin-left: 161px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
|
||||||
|
HTTP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="161" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||||
|
HTTP
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="297" y="60.29" width="70.29" height="38.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 80px; margin-left: 298px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
(1)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="332" y="83" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
(1)
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="566.43" y="60.29" width="70.29" height="38.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 80px; margin-left: 567px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
(2)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="602" y="83" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
(2)
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="497.31" y="239" width="70.29" height="38.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 258px; margin-left: 498px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
(3)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="532" y="262" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
(3)
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="704.66" y="142.57" width="70.29" height="38.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 162px; margin-left: 706px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
(3)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="740" y="165" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
(3)
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="384.86" y="44" width="70.29" height="28.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 58px; margin-left: 386px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
Container
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="420" y="62" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
Container
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
<rect x="636.71" y="44" width="70.29" height="28.57" fill="none" stroke="none" pointer-events="all"/>
|
||||||
|
<g transform="translate(-0.5 -0.5)">
|
||||||
|
<switch>
|
||||||
|
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
|
||||||
|
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 58px; margin-left: 638px;">
|
||||||
|
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||||
|
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; overflow-wrap: normal;">
|
||||||
|
Container
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</foreignObject>
|
||||||
|
<text x="672" y="62" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||||
|
Container
|
||||||
|
</text>
|
||||||
|
</switch>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<switch>
|
||||||
|
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||||
|
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
|
||||||
|
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||||
|
Viewer does not support full SVG 1.1
|
||||||
|
</text>
|
||||||
|
</a>
|
||||||
|
</switch>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 25 KiB |
@ -26,8 +26,8 @@ To deploy mealie on your local network it is highly recommended to use docker to
|
|||||||
Due to a build dependency limitation, Mealie is not supported on 32bit ARM systems. If you're running into this limitation on a newer Raspberry Pi, please consider upgrading to a 64bit operating system on the Raspberry Pi.
|
Due to a build dependency limitation, Mealie is not supported on 32bit ARM systems. If you're running into this limitation on a newer Raspberry Pi, please consider upgrading to a 64bit operating system on the Raspberry Pi.
|
||||||
|
|
||||||
|
|
||||||
## Step 1: Deciding on Deployment Type
|
## Step 1: Deployment Type
|
||||||
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users and your concurrent write operations will be some-what limited. If you need to support many concurrent users, you may want to consider a more robust database such as PostgreSQL.
|
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users and your concurrent write operations will be some-what limited. If you need to support many concurrent users, you may want to consider a more robust database such as PostgreSQL.
|
||||||
|
|
||||||
You can find the relevant ready to use docker-compose files for supported installations at the links below.
|
You can find the relevant ready to use docker-compose files for supported installations at the links below.
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ You can find the relevant ready to use docker-compose files for supported instal
|
|||||||
|
|
||||||
## Step 2: Setting up your files.
|
## Step 2: Setting up your files.
|
||||||
|
|
||||||
The following steps were tested on a Ubuntu 20.04 server, but should work for most other Linux distributions. These steps are not required, but is how I generally will setup services on my server.
|
The following steps were tested on a Ubuntu 20.04 server, but should work for most other Linux distributions. These steps are not required, but is how I generally will setup services on my server.
|
||||||
|
|
||||||
|
|
||||||
1. SSH into your server and navigate to the home directory of the user you want to run Mealie as. If that is your current user, you can use `cd ~` to ensure you're in the right directory.
|
1. SSH into your server and navigate to the home directory of the user you want to run Mealie as. If that is your current user, you can use `cd ~` to ensure you're in the right directory.
|
||||||
@ -56,8 +56,8 @@ After you've decided setup the files it's important to set a few ENV variables t
|
|||||||
- [x] You've set the `DEFAULT_EMAIL` and `DEFAULT_GROUP` variable.
|
- [x] You've set the `DEFAULT_EMAIL` and `DEFAULT_GROUP` variable.
|
||||||
- [x] Make any theme changes on the frontend container. [See Frontend Config](./frontend-config.md#themeing)
|
- [x] Make any theme changes on the frontend container. [See Frontend Config](./frontend-config.md#themeing)
|
||||||
|
|
||||||
## Step 3: Startup
|
## Step 3: Startup
|
||||||
After you've configured your database, and updated the `docker-compose.yaml` files, you can start Mealie by running the following command in the directory where you've added your `docker-compose.yaml`.
|
After you've configured your database, and updated the `docker-compose.yaml` files, you can start Mealie by running the following command in the directory where you've added your `docker-compose.yaml`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker-compose up -d
|
$ docker-compose up -d
|
||||||
@ -71,7 +71,28 @@ You should see the containers start up without error. You should now be able to
|
|||||||
|
|
||||||
**Password:** MyPassword
|
**Password:** MyPassword
|
||||||
|
|
||||||
## Step 4: Backup
|
## Step 4: Validate Installation
|
||||||
While v1.0.0 is a great step to data-stability and security, it's not a backup. As a core feature, Mealie will run a backup every 24 hours. Optionally, you can also run backups whenever you'd like through the UI or the API.
|
|
||||||
|
|
||||||
These backups are just plain .zip files that you can download from the UI or access via the mounted volume on your system. For complete data protection you MUST store these backups somewhere safe, and outside of the server where they are deployed. A favorite solution of mine is [autorestic](https://autorestic.vercel.app/) which can be configured via yaml to run an off-site backup on a regular basis.
|
After the startup is complete you should see a login screen. Use the default credentials above to login and navigate to `/admin/site-settings`. Here you'll find a summary of your configuration details and their respective status. Before proceeding you should validate that the configuration is correct. For any warnings or errors the page will display an error and notify you of what you need to verify.
|
||||||
|
|
||||||
|
!!! tip "Docker Volume"
|
||||||
|
Mealie uses a shared data-volume between the Backend and Frontend containers for images and assets. Ensure that this is configured correctly by using the "Docker Volume Test" section in the settings page. Running this validation will ensure that you have configured your volumes correctly. Mealie will not work correctly without this configured correctly.
|
||||||
|
|
||||||
|
## Step 5: Backup
|
||||||
|
While v1.0.0 is a great step to data-stability and security, it's not a backup. Mealie provides a full site data backup mechanism through the UI.
|
||||||
|
|
||||||
|
These backups are just plain .zip files that you can download from the UI or access via the mounted volume on your system. For complete data protection you MUST store these backups somewhere safe, and outside of the server where they are deployed.
|
||||||
|
|
||||||
|
## Appendix
|
||||||
|
|
||||||
|
### Docker Diagram
|
||||||
|
|
||||||
|
While the docker-compose file should work without modification, some users want to tailor it to their installation. This diagram shows network and volume architecture for the default setup. You can use this to help you customize your configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In the diagram above there's a few crutial things to note.
|
||||||
|
|
||||||
|
1. Port 9925 is the host port, this can be anything you want. The important part is that it's mapped to the mealie-frontend container at port 3000.
|
||||||
|
2. The mealie-frontend container communicated with the mealie-api container through the INTERNAL docker network. This requires that the two containers are on the same network and that the network supports name resolution (anything but the default bridge network). The resolution URL can be specified in the docker-compose as the `API_URL` environment variable.
|
||||||
|
3. The mealie-data volume is mounted to BOTH the mealie-frontend and mealie-api containers. This is REQUIRED to ensure that images and assets are severed up correctly. While the default configuration is a docker-volume, that same can be accomplished by using a local directory mounted to the containers.
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# Installing with PostgreSQL
|
# Installing with PostgreSQL
|
||||||
|
|
||||||
Postgres support was introduced in v0.5.0. At this point it should be used with caution and frequent backups.
|
|
||||||
|
|
||||||
**For Environmental Variable Configuration See:**
|
**For Environmental Variable Configuration See:**
|
||||||
|
|
||||||
- [Frontend Configuration](/mealie/documentation/getting-started/installation/frontend-config/)
|
- [Frontend Configuration](/mealie/documentation/getting-started/installation/frontend-config/)
|
||||||
|
@ -20,13 +20,19 @@
|
|||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
|
|
||||||
# Handles User Images
|
# Handles User Images
|
||||||
handle_path /api/media/users/* {
|
handle_path /api/media/users/* {
|
||||||
header @static Cache-Control max-age=31536000
|
header @static Cache-Control max-age=31536000
|
||||||
root * /app/data/users/
|
root * /app/data/users/
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Handle Docker Volume Validation File
|
||||||
|
handle_path /api/media/docker/* {
|
||||||
|
root * /app/data/docker-validation/
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
handle @apidocs {
|
handle @apidocs {
|
||||||
uri strip_suffix /
|
uri strip_suffix /
|
||||||
@ -37,4 +43,4 @@
|
|||||||
uri strip_suffix /
|
uri strip_suffix /
|
||||||
reverse_proxy http://127.0.0.1:3001
|
reverse_proxy http://127.0.0.1:3001
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BaseAPI } from "../_base";
|
import { BaseAPI } from "../_base";
|
||||||
import { AdminAboutInfo } from "~/types/api-types/admin";
|
import { AdminAboutInfo, DockerVolumeText, CheckAppConfig } from "~/types/api-types/admin";
|
||||||
|
|
||||||
const prefix = "/api";
|
const prefix = "/api";
|
||||||
|
|
||||||
@ -7,25 +7,10 @@ const routes = {
|
|||||||
about: `${prefix}/admin/about`,
|
about: `${prefix}/admin/about`,
|
||||||
aboutStatistics: `${prefix}/admin/about/statistics`,
|
aboutStatistics: `${prefix}/admin/about/statistics`,
|
||||||
check: `${prefix}/admin/about/check`,
|
check: `${prefix}/admin/about/check`,
|
||||||
|
docker: `${prefix}/admin/about/docker/validate`,
|
||||||
|
validationFile: `${prefix}/media/docker/validate.txt`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface AdminStatistics {
|
|
||||||
totalRecipes: number;
|
|
||||||
totalUsers: number;
|
|
||||||
totalGroups: number;
|
|
||||||
uncategorizedRecipes: number;
|
|
||||||
untaggedRecipes: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CheckAppConfig {
|
|
||||||
emailReady: boolean;
|
|
||||||
baseUrlSet: boolean;
|
|
||||||
isSiteSecure: boolean;
|
|
||||||
isUpToDate: boolean;
|
|
||||||
ldapReady: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdminAboutAPI extends BaseAPI {
|
export class AdminAboutAPI extends BaseAPI {
|
||||||
async about() {
|
async about() {
|
||||||
return await this.requests.get<AdminAboutInfo>(routes.about);
|
return await this.requests.get<AdminAboutInfo>(routes.about);
|
||||||
@ -38,4 +23,12 @@ export class AdminAboutAPI extends BaseAPI {
|
|||||||
async checkApp() {
|
async checkApp() {
|
||||||
return await this.requests.get<CheckAppConfig>(routes.check);
|
return await this.requests.get<CheckAppConfig>(routes.check);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkDocker() {
|
||||||
|
return await this.requests.get<DockerVolumeText>(routes.docker);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDockerValidateFileContents() {
|
||||||
|
return await this.requests.get<string>(routes.validationFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<v-menu top offset-y left open-on-hover>
|
<v-menu top offset-y left open-on-hover>
|
||||||
<template #activator="{ on, attrs }">
|
<template #activator="{ on, attrs }">
|
||||||
<v-btn icon v-bind="attrs" v-on="on" @click.stop>
|
<v-btn :small="small" icon v-bind="attrs" v-on="on" @click.stop>
|
||||||
<v-icon> {{ $globals.icons.help }} </v-icon>
|
<v-icon :small="small"> {{ $globals.icons.help }} </v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card max-width="300px">
|
<v-card max-width="300px">
|
||||||
@ -19,8 +19,11 @@
|
|||||||
import { defineComponent } from "@nuxtjs/composition-api";
|
import { defineComponent } from "@nuxtjs/composition-api";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
props: {
|
||||||
return {};
|
small: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -8,26 +8,67 @@
|
|||||||
</BasePageTitle>
|
</BasePageTitle>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" title="General Configuration">
|
<BaseCardSectionTitle class="pb-0" :icon="$globals.icons.cog" title="Configuration"> </BaseCardSectionTitle>
|
||||||
</BaseCardSectionTitle>
|
<v-card class="mb-4">
|
||||||
|
<template v-for="(check, idx) in simpleChecks">
|
||||||
|
<v-list-item :key="`list-item-${idx}`">
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-icon :color="check.color">
|
||||||
|
{{ check.icon }}
|
||||||
|
</v-icon>
|
||||||
|
</v-list-item-icon>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title>
|
||||||
|
{{ check.text }}
|
||||||
|
</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="wrap-word">
|
||||||
|
{{ check.status ? check.successText : check.errorText }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider :key="`divider-${idx}`"></v-divider>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.docker" title="Docker Volume" />
|
||||||
<v-alert
|
<v-alert
|
||||||
v-for="(check, idx) in simpleChecks"
|
|
||||||
:key="idx"
|
|
||||||
border="left"
|
border="left"
|
||||||
colored-border
|
colored-border
|
||||||
:type="getColor(check.status, check.warning)"
|
:type="docker.state === DockerVolumeState.Error ? 'error' : 'info'"
|
||||||
|
:icon="$globals.icons.docker"
|
||||||
elevation="2"
|
elevation="2"
|
||||||
|
:loading="docker.loading"
|
||||||
>
|
>
|
||||||
<div class="font-weight-medium">{{ check.text }}</div>
|
<div class="d-flex align-center font-weight-medium">
|
||||||
|
Docker Volume
|
||||||
|
<HelpIcon small class="my-n3">
|
||||||
|
Mealie requires that the frontend container and the backend share the same docker volume or storage. This
|
||||||
|
ensures that the frontend container can properly access the images and assets stored on disk.
|
||||||
|
</HelpIcon>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{ check.status ? check.successText : check.errorText }}
|
<template v-if="docker.state === DockerVolumeState.Error"> Volumes are misconfigured. </template>
|
||||||
|
<template v-else-if="docker.state === DockerVolumeState.Success">
|
||||||
|
Volumes are configured correctly.
|
||||||
|
</template>
|
||||||
|
<template v-else-if="docker.state === DockerVolumeState.Unknown">
|
||||||
|
Status Unknown. Try running a validation.
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<BaseButton color="info" :loading="docker.loading" @click="dockerValidate">
|
||||||
|
<template #icon> {{ $globals.icons.checkboxMarkedCircle }} </template>
|
||||||
|
Validate
|
||||||
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</v-alert>
|
</v-alert>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" title="Email Configuration" />
|
<BaseCardSectionTitle class="pt-2" :icon="$globals.icons.email" title="Email" />
|
||||||
<v-alert border="left" colored-border :type="getColor(appConfig.emailReady)" elevation="2">
|
<v-alert border="left" colored-border :type="appConfig.emailReady ? 'success' : 'error'" elevation="2">
|
||||||
<div class="font-weight-medium">Email Configuration Status</div>
|
<div class="font-weight-medium">Email Configuration Status</div>
|
||||||
<div>
|
<div>
|
||||||
{{ appConfig.emailReady ? "Ready" : "Not Ready - Check Environmental Variables" }}
|
{{ appConfig.emailReady ? "Ready" : "Not Ready - Check Environmental Variables" }}
|
||||||
@ -51,13 +92,6 @@
|
|||||||
<span class="pl-4">
|
<span class="pl-4">
|
||||||
{{ success ? "Succeeded" : "Failed" }}
|
{{ success ? "Succeeded" : "Failed" }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- <template v-if="errors">
|
|
||||||
<h4>Errors:</h4>
|
|
||||||
<span class="pl-4">
|
|
||||||
{{ errors }}
|
|
||||||
</span>
|
|
||||||
</template> -->
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -95,22 +129,62 @@ import {
|
|||||||
useAsync,
|
useAsync,
|
||||||
useContext,
|
useContext,
|
||||||
} from "@nuxtjs/composition-api";
|
} from "@nuxtjs/composition-api";
|
||||||
import { CheckAppConfig } from "~/api/admin/admin-about";
|
|
||||||
import { useAdminApi, useUserApi } from "~/composables/api";
|
import { useAdminApi, useUserApi } from "~/composables/api";
|
||||||
import { validators } from "~/composables/use-validators";
|
import { validators } from "~/composables/use-validators";
|
||||||
import { useAsyncKey } from "~/composables/use-utils";
|
import { useAsyncKey } from "~/composables/use-utils";
|
||||||
|
import { CheckAppConfig } from "~/types/api-types/admin";
|
||||||
|
|
||||||
|
enum DockerVolumeState {
|
||||||
|
Unknown = "unknown",
|
||||||
|
Success = "success",
|
||||||
|
Error = "error",
|
||||||
|
}
|
||||||
|
|
||||||
interface SimpleCheck {
|
interface SimpleCheck {
|
||||||
status: boolean;
|
|
||||||
text: string;
|
text: string;
|
||||||
|
status: boolean | undefined;
|
||||||
successText: string;
|
successText: string;
|
||||||
errorText: string;
|
errorText: string;
|
||||||
warning: boolean;
|
color: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CheckApp extends CheckAppConfig {
|
||||||
|
isSiteSecure?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
setup() {
|
setup() {
|
||||||
|
// ==========================================================
|
||||||
|
// Docker Volume Validation
|
||||||
|
const docker = reactive({
|
||||||
|
loading: false,
|
||||||
|
state: DockerVolumeState.Unknown,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function dockerValidate() {
|
||||||
|
docker.loading = true;
|
||||||
|
|
||||||
|
// Do API Check
|
||||||
|
const { data } = await adminApi.about.checkDocker();
|
||||||
|
if (data == null) {
|
||||||
|
docker.state = DockerVolumeState.Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get File Contents
|
||||||
|
const { data: fileContents } = await adminApi.about.getDockerValidateFileContents();
|
||||||
|
|
||||||
|
if (data.text === fileContents) {
|
||||||
|
docker.state = DockerVolumeState.Success;
|
||||||
|
} else {
|
||||||
|
docker.state = DockerVolumeState.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
docker.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
loading: false,
|
loading: false,
|
||||||
address: "",
|
address: "",
|
||||||
@ -119,17 +193,21 @@ export default defineComponent({
|
|||||||
tested: false,
|
tested: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const appConfig = ref<CheckAppConfig>({
|
const appConfig = ref<CheckApp>({
|
||||||
emailReady: false,
|
emailReady: true,
|
||||||
baseUrlSet: false,
|
baseUrlSet: true,
|
||||||
isSiteSecure: false,
|
isSiteSecure: true,
|
||||||
isUpToDate: false,
|
isUpToDate: false,
|
||||||
ldapReady: false,
|
ldapReady: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const api = useUserApi();
|
function isLocalHostOrHttps() {
|
||||||
|
return window.location.hostname === "localhost" || window.location.protocol === "https:";
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = useUserApi();
|
||||||
const adminApi = useAdminApi();
|
const adminApi = useAdminApi();
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const { data } = await adminApi.about.checkApp();
|
const { data } = await adminApi.about.checkApp();
|
||||||
|
|
||||||
@ -140,43 +218,53 @@ export default defineComponent({
|
|||||||
appConfig.value.isSiteSecure = isLocalHostOrHttps();
|
appConfig.value.isSiteSecure = isLocalHostOrHttps();
|
||||||
});
|
});
|
||||||
|
|
||||||
function isLocalHostOrHttps() {
|
|
||||||
return window.location.hostname === "localhost" || window.location.protocol === "https:";
|
|
||||||
}
|
|
||||||
|
|
||||||
const simpleChecks = computed<SimpleCheck[]>(() => {
|
const simpleChecks = computed<SimpleCheck[]>(() => {
|
||||||
return [
|
const goodIcon = $globals.icons.checkboxMarkedCircle;
|
||||||
|
const badIcon = $globals.icons.alert;
|
||||||
|
const warningIcon = $globals.icons.alertCircle;
|
||||||
|
|
||||||
|
const goodColor = "success";
|
||||||
|
const badColor = "error";
|
||||||
|
const warningColor = "warning";
|
||||||
|
|
||||||
|
const data: SimpleCheck[] = [
|
||||||
{
|
{
|
||||||
status: appConfig.value.isUpToDate,
|
|
||||||
text: "Application Version",
|
text: "Application Version",
|
||||||
|
status: appConfig.value.isUpToDate,
|
||||||
errorText: `Your current version (${rawAppInfo.value.version}) does not match the latest release. Considering updating to the latest version (${rawAppInfo.value.versionLatest}).`,
|
errorText: `Your current version (${rawAppInfo.value.version}) does not match the latest release. Considering updating to the latest version (${rawAppInfo.value.versionLatest}).`,
|
||||||
successText: "Mealie is up to date",
|
successText: "Mealie is up to date",
|
||||||
warning: true,
|
color: appConfig.value.isUpToDate ? goodColor : warningColor,
|
||||||
|
icon: appConfig.value.isUpToDate ? goodIcon : warningIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: appConfig.value.isSiteSecure,
|
|
||||||
text: "Secure Site",
|
text: "Secure Site",
|
||||||
|
status: appConfig.value.isSiteSecure,
|
||||||
errorText: "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
|
errorText: "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
|
||||||
successText: "Site is accessed by localhost or https",
|
successText: "Site is accessed by localhost or https",
|
||||||
warning: false,
|
color: appConfig.value.isSiteSecure ? goodColor : badColor,
|
||||||
|
icon: appConfig.value.isSiteSecure ? goodIcon : badIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: appConfig.value.baseUrlSet,
|
|
||||||
text: "Server Side Base URL",
|
text: "Server Side Base URL",
|
||||||
|
status: appConfig.value.baseUrlSet,
|
||||||
errorText:
|
errorText:
|
||||||
"`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
|
"`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
|
||||||
successText: "Server Side URL does not match the default",
|
successText: "Server Side URL does not match the default",
|
||||||
warning: false,
|
color: appConfig.value.baseUrlSet ? goodColor : badColor,
|
||||||
|
icon: appConfig.value.baseUrlSet ? goodIcon : badIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
status: appConfig.value.ldapReady,
|
|
||||||
text: "LDAP Ready",
|
text: "LDAP Ready",
|
||||||
|
status: appConfig.value.ldapReady,
|
||||||
errorText:
|
errorText:
|
||||||
"Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
"Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
|
||||||
successText: "Required LDAP variables are all set.",
|
successText: "Required LDAP variables are all set.",
|
||||||
warning: true,
|
color: appConfig.value.ldapReady ? goodColor : warningColor,
|
||||||
|
icon: appConfig.value.ldapReady ? goodIcon : warningIcon,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function testEmail() {
|
async function testEmail() {
|
||||||
@ -209,11 +297,6 @@ export default defineComponent({
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
function getColor(booly: unknown, warning = false) {
|
|
||||||
const falsey = warning ? "warning" : "error";
|
|
||||||
return booly ? "success" : falsey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// General About Info
|
// General About Info
|
||||||
|
|
||||||
@ -292,8 +375,10 @@ export default defineComponent({
|
|||||||
const appInfo = getAppInfo();
|
const appInfo = getAppInfo();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
DockerVolumeState,
|
||||||
|
docker,
|
||||||
|
dockerValidate,
|
||||||
simpleChecks,
|
simpleChecks,
|
||||||
getColor,
|
|
||||||
appConfig,
|
appConfig,
|
||||||
validEmail,
|
validEmail,
|
||||||
validators,
|
validators,
|
||||||
@ -310,4 +395,9 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped>
|
||||||
|
.wrap-word {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -170,6 +170,9 @@ export interface CustomPageOut {
|
|||||||
categories?: RecipeCategoryResponse[];
|
categories?: RecipeCategoryResponse[];
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
export interface DockerVolumeText {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
export interface GroupImport {
|
export interface GroupImport {
|
||||||
name: string;
|
name: string;
|
||||||
status: boolean;
|
status: boolean;
|
||||||
|
132
frontend/types/components.d.ts
vendored
132
frontend/types/components.d.ts
vendored
@ -1,76 +1,74 @@
|
|||||||
// This Code is auto generated by gen_global_components.py
|
// This Code is auto generated by gen_global_components.py
|
||||||
import BaseCardSectionTitle from "@/components/global/BaseCardSectionTitle.vue";
|
import BaseCardSectionTitle from "@/components/global/BaseCardSectionTitle.vue";
|
||||||
import MarkdownEditor from "@/components/global/MarkdownEditor.vue";
|
import MarkdownEditor from "@/components/global/MarkdownEditor.vue";
|
||||||
import AppLoader from "@/components/global/AppLoader.vue";
|
import AppLoader from "@/components/global/AppLoader.vue";
|
||||||
import BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
import BaseOverflowButton from "@/components/global/BaseOverflowButton.vue";
|
||||||
import ReportTable from "@/components/global/ReportTable.vue";
|
import ReportTable from "@/components/global/ReportTable.vue";
|
||||||
import AppToolbar from "@/components/global/AppToolbar.vue";
|
import AppToolbar from "@/components/global/AppToolbar.vue";
|
||||||
import BaseButtonGroup from "@/components/global/BaseButtonGroup.vue";
|
import BaseButtonGroup from "@/components/global/BaseButtonGroup.vue";
|
||||||
import BaseButton from "@/components/global/BaseButton.vue";
|
import BaseButton from "@/components/global/BaseButton.vue";
|
||||||
import BannerExperimental from "@/components/global/BannerExperimental.vue";
|
import BannerExperimental from "@/components/global/BannerExperimental.vue";
|
||||||
import BaseDialog from "@/components/global/BaseDialog.vue";
|
import BaseDialog from "@/components/global/BaseDialog.vue";
|
||||||
import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue";
|
import RecipeJsonEditor from "@/components/global/RecipeJsonEditor.vue";
|
||||||
import StatsCards from "@/components/global/StatsCards.vue";
|
import StatsCards from "@/components/global/StatsCards.vue";
|
||||||
import HelpIcon from "@/components/global/HelpIcon.vue";
|
import HelpIcon from "@/components/global/HelpIcon.vue";
|
||||||
import InputLabelType from "@/components/global/InputLabelType.vue";
|
import InputLabelType from "@/components/global/InputLabelType.vue";
|
||||||
import BaseStatCard from "@/components/global/BaseStatCard.vue";
|
import BaseStatCard from "@/components/global/BaseStatCard.vue";
|
||||||
import DevDumpJson from "@/components/global/DevDumpJson.vue";
|
import DevDumpJson from "@/components/global/DevDumpJson.vue";
|
||||||
import LanguageDialog from "@/components/global/LanguageDialog.vue";
|
import LanguageDialog from "@/components/global/LanguageDialog.vue";
|
||||||
import InputQuantity from "@/components/global/InputQuantity.vue";
|
import InputQuantity from "@/components/global/InputQuantity.vue";
|
||||||
import ToggleState from "@/components/global/ToggleState.vue";
|
import ToggleState from "@/components/global/ToggleState.vue";
|
||||||
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
|
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
|
||||||
import CrudTable from "@/components/global/CrudTable.vue";
|
import CrudTable from "@/components/global/CrudTable.vue";
|
||||||
import InputColor from "@/components/global/InputColor.vue";
|
import InputColor from "@/components/global/InputColor.vue";
|
||||||
import BaseDivider from "@/components/global/BaseDivider.vue";
|
import BaseDivider from "@/components/global/BaseDivider.vue";
|
||||||
import AutoForm from "@/components/global/AutoForm.vue";
|
import AutoForm from "@/components/global/AutoForm.vue";
|
||||||
import AppButtonUpload from "@/components/global/AppButtonUpload.vue";
|
import AppButtonUpload from "@/components/global/AppButtonUpload.vue";
|
||||||
import AdvancedOnly from "@/components/global/AdvancedOnly.vue";
|
import AdvancedOnly from "@/components/global/AdvancedOnly.vue";
|
||||||
import BasePageTitle from "@/components/global/BasePageTitle.vue";
|
import BasePageTitle from "@/components/global/BasePageTitle.vue";
|
||||||
import ButtonLink from "@/components/global/ButtonLink.vue";
|
import ButtonLink from "@/components/global/ButtonLink.vue";
|
||||||
|
|
||||||
import TheSnackbar from "@/components/layout/TheSnackbar.vue";
|
|
||||||
import AppHeader from "@/components/layout/AppHeader.vue";
|
|
||||||
import AppSidebar from "@/components/layout/AppSidebar.vue";
|
|
||||||
import AppFooter from "@/components/layout/AppFooter.vue";
|
|
||||||
|
|
||||||
|
import TheSnackbar from "@/components/layout/TheSnackbar.vue";
|
||||||
|
import AppHeader from "@/components/layout/AppHeader.vue";
|
||||||
|
import AppSidebar from "@/components/layout/AppSidebar.vue";
|
||||||
|
import AppFooter from "@/components/layout/AppFooter.vue";
|
||||||
|
|
||||||
declare module "vue" {
|
declare module "vue" {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
// Global Components
|
// Global Components
|
||||||
BaseCardSectionTitle: typeof BaseCardSectionTitle;
|
BaseCardSectionTitle: typeof BaseCardSectionTitle;
|
||||||
MarkdownEditor: typeof MarkdownEditor;
|
MarkdownEditor: typeof MarkdownEditor;
|
||||||
AppLoader: typeof AppLoader;
|
AppLoader: typeof AppLoader;
|
||||||
BaseOverflowButton: typeof BaseOverflowButton;
|
BaseOverflowButton: typeof BaseOverflowButton;
|
||||||
ReportTable: typeof ReportTable;
|
ReportTable: typeof ReportTable;
|
||||||
AppToolbar: typeof AppToolbar;
|
AppToolbar: typeof AppToolbar;
|
||||||
BaseButtonGroup: typeof BaseButtonGroup;
|
BaseButtonGroup: typeof BaseButtonGroup;
|
||||||
BaseButton: typeof BaseButton;
|
BaseButton: typeof BaseButton;
|
||||||
BannerExperimental: typeof BannerExperimental;
|
BannerExperimental: typeof BannerExperimental;
|
||||||
BaseDialog: typeof BaseDialog;
|
BaseDialog: typeof BaseDialog;
|
||||||
RecipeJsonEditor: typeof RecipeJsonEditor;
|
RecipeJsonEditor: typeof RecipeJsonEditor;
|
||||||
StatsCards: typeof StatsCards;
|
StatsCards: typeof StatsCards;
|
||||||
HelpIcon: typeof HelpIcon;
|
HelpIcon: typeof HelpIcon;
|
||||||
InputLabelType: typeof InputLabelType;
|
InputLabelType: typeof InputLabelType;
|
||||||
BaseStatCard: typeof BaseStatCard;
|
BaseStatCard: typeof BaseStatCard;
|
||||||
DevDumpJson: typeof DevDumpJson;
|
DevDumpJson: typeof DevDumpJson;
|
||||||
LanguageDialog: typeof LanguageDialog;
|
LanguageDialog: typeof LanguageDialog;
|
||||||
InputQuantity: typeof InputQuantity;
|
InputQuantity: typeof InputQuantity;
|
||||||
ToggleState: typeof ToggleState;
|
ToggleState: typeof ToggleState;
|
||||||
AppButtonCopy: typeof AppButtonCopy;
|
AppButtonCopy: typeof AppButtonCopy;
|
||||||
CrudTable: typeof CrudTable;
|
CrudTable: typeof CrudTable;
|
||||||
InputColor: typeof InputColor;
|
InputColor: typeof InputColor;
|
||||||
BaseDivider: typeof BaseDivider;
|
BaseDivider: typeof BaseDivider;
|
||||||
AutoForm: typeof AutoForm;
|
AutoForm: typeof AutoForm;
|
||||||
AppButtonUpload: typeof AppButtonUpload;
|
AppButtonUpload: typeof AppButtonUpload;
|
||||||
AdvancedOnly: typeof AdvancedOnly;
|
AdvancedOnly: typeof AdvancedOnly;
|
||||||
BasePageTitle: typeof BasePageTitle;
|
BasePageTitle: typeof BasePageTitle;
|
||||||
ButtonLink: typeof ButtonLink;
|
ButtonLink: typeof ButtonLink;
|
||||||
// Layout Components
|
// Layout Components
|
||||||
TheSnackbar: typeof TheSnackbar;
|
TheSnackbar: typeof TheSnackbar;
|
||||||
AppHeader: typeof AppHeader;
|
AppHeader: typeof AppHeader;
|
||||||
AppSidebar: typeof AppSidebar;
|
AppSidebar: typeof AppSidebar;
|
||||||
AppFooter: typeof AppFooter;
|
AppFooter: typeof AppFooter;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ export interface Icon {
|
|||||||
primary: string;
|
primary: string;
|
||||||
|
|
||||||
// General
|
// General
|
||||||
|
docker: string;
|
||||||
chart: string;
|
chart: string;
|
||||||
wrench: string;
|
wrench: string;
|
||||||
help: string;
|
help: string;
|
||||||
|
@ -108,6 +108,7 @@ import {
|
|||||||
mdiWrench,
|
mdiWrench,
|
||||||
mdiChartLine,
|
mdiChartLine,
|
||||||
mdiHelpCircleOutline,
|
mdiHelpCircleOutline,
|
||||||
|
mdiDocker,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
@ -116,6 +117,7 @@ export const icons = {
|
|||||||
|
|
||||||
wrench: mdiWrench,
|
wrench: mdiWrench,
|
||||||
chart: mdiChartLine,
|
chart: mdiChartLine,
|
||||||
|
docker: mdiDocker,
|
||||||
|
|
||||||
// General
|
// General
|
||||||
bowlMixOutline: mdiBowlMixOutline,
|
bowlMixOutline: mdiBowlMixOutline,
|
||||||
|
@ -130,6 +130,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
"recipe_ingredient",
|
"recipe_ingredient",
|
||||||
"recipe_instructions",
|
"recipe_instructions",
|
||||||
"settings",
|
"settings",
|
||||||
|
"comments",
|
||||||
}
|
}
|
||||||
|
|
||||||
@validates("name")
|
@validates("name")
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
from fastapi import APIRouter
|
import asyncio
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import string
|
||||||
|
|
||||||
|
from fastapi import APIRouter, BackgroundTasks
|
||||||
|
|
||||||
from mealie.core.release_checker import get_latest_version
|
from mealie.core.release_checker import get_latest_version
|
||||||
from mealie.core.settings.static import APP_VERSION
|
from mealie.core.settings.static import APP_VERSION
|
||||||
from mealie.routes._base import BaseAdminController, controller
|
from mealie.routes._base import BaseAdminController, controller
|
||||||
from mealie.schema.admin.about import AdminAboutInfo, AppStatistics, CheckAppConfig
|
from mealie.schema.admin.about import AdminAboutInfo, AppStatistics, CheckAppConfig, DockerVolumeText
|
||||||
|
|
||||||
router = APIRouter(prefix="/about")
|
router = APIRouter(prefix="/about")
|
||||||
|
|
||||||
@ -11,7 +16,7 @@ router = APIRouter(prefix="/about")
|
|||||||
@controller(router)
|
@controller(router)
|
||||||
class AdminAboutController(BaseAdminController):
|
class AdminAboutController(BaseAdminController):
|
||||||
@router.get("", response_model=AdminAboutInfo)
|
@router.get("", response_model=AdminAboutInfo)
|
||||||
async def get_app_info(self):
|
def get_app_info(self):
|
||||||
"""Get general application information"""
|
"""Get general application information"""
|
||||||
settings = self.deps.settings
|
settings = self.deps.settings
|
||||||
|
|
||||||
@ -30,18 +35,18 @@ class AdminAboutController(BaseAdminController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@router.get("/statistics", response_model=AppStatistics)
|
@router.get("/statistics", response_model=AppStatistics)
|
||||||
async def get_app_statistics(self):
|
def get_app_statistics(self):
|
||||||
|
|
||||||
return AppStatistics(
|
return AppStatistics(
|
||||||
total_recipes=self.repos.recipes.count_all(),
|
total_recipes=self.repos.recipes.count_all(),
|
||||||
uncategorized_recipes=self.repos.recipes.count_uncategorized(),
|
uncategorized_recipes=self.repos.recipes.count_uncategorized(), # type: ignore
|
||||||
untagged_recipes=self.repos.recipes.count_untagged(),
|
untagged_recipes=self.repos.recipes.count_untagged(), # type: ignore
|
||||||
total_users=self.repos.users.count_all(),
|
total_users=self.repos.users.count_all(),
|
||||||
total_groups=self.repos.groups.count_all(),
|
total_groups=self.repos.groups.count_all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get("/check", response_model=CheckAppConfig)
|
@router.get("/check", response_model=CheckAppConfig)
|
||||||
async def check_app_config(self):
|
def check_app_config(self):
|
||||||
settings = self.deps.settings
|
settings = self.deps.settings
|
||||||
url_set = settings.BASE_URL != "http://localhost:8080"
|
url_set = settings.BASE_URL != "http://localhost:8080"
|
||||||
|
|
||||||
@ -51,3 +56,25 @@ class AdminAboutController(BaseAdminController):
|
|||||||
base_url_set=url_set,
|
base_url_set=url_set,
|
||||||
is_up_to_date=get_latest_version() == APP_VERSION,
|
is_up_to_date=get_latest_version() == APP_VERSION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.get("/docker/validate", response_model=DockerVolumeText)
|
||||||
|
def validate_docker_volume(self, bg: BackgroundTasks):
|
||||||
|
validation_dir = self.deps.folders.DATA_DIR / "docker-validation"
|
||||||
|
validation_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
random_string = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
|
||||||
|
|
||||||
|
with validation_dir.joinpath("validate.txt").open("w") as f:
|
||||||
|
f.write(random_string)
|
||||||
|
|
||||||
|
async def cleanup():
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.rmtree(validation_dir)
|
||||||
|
except Exception as e:
|
||||||
|
self.deps.logger.error(f"Failed to remove docker validation directory: {e}")
|
||||||
|
|
||||||
|
bg.add_task(cleanup)
|
||||||
|
|
||||||
|
return DockerVolumeText(text=random_string)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
from mealie.core.config import get_app_dirs
|
||||||
|
|
||||||
from . import media_recipe, media_user
|
from . import media_recipe, media_user
|
||||||
|
|
||||||
@ -6,3 +9,15 @@ media_router = APIRouter(prefix="/api/media", tags=["Recipe: Images and Assets"]
|
|||||||
|
|
||||||
media_router.include_router(media_recipe.router)
|
media_router.include_router(media_recipe.router)
|
||||||
media_router.include_router(media_user.router)
|
media_router.include_router(media_user.router)
|
||||||
|
|
||||||
|
|
||||||
|
@media_router.get("/docker/validate.txt", response_class=FileResponse)
|
||||||
|
async def get_validation_text():
|
||||||
|
folders = get_app_dirs()
|
||||||
|
|
||||||
|
file = folders.DATA_DIR / "docker-validation" / "validate.txt"
|
||||||
|
|
||||||
|
if file.exists():
|
||||||
|
return file
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=404, detail="File not found")
|
||||||
|
@ -31,3 +31,7 @@ class CheckAppConfig(MealieModel):
|
|||||||
ldap_ready: bool = False
|
ldap_ready: bool = False
|
||||||
base_url_set: bool = False
|
base_url_set: bool = False
|
||||||
is_up_to_date: bool = False
|
is_up_to_date: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class DockerVolumeText(MealieModel):
|
||||||
|
text: str
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from pydantic import UUID4
|
from pydantic import UUID4
|
||||||
|
|
||||||
@ -8,7 +7,7 @@ from mealie.schema._mealie import MealieModel
|
|||||||
|
|
||||||
|
|
||||||
class UserBase(MealieModel):
|
class UserBase(MealieModel):
|
||||||
id: int
|
id: UUID4
|
||||||
username: Optional[str]
|
username: Optional[str]
|
||||||
admin: bool
|
admin: bool
|
||||||
|
|
||||||
@ -26,12 +25,12 @@ class RecipeCommentSave(RecipeCommentCreate):
|
|||||||
|
|
||||||
|
|
||||||
class RecipeCommentUpdate(MealieModel):
|
class RecipeCommentUpdate(MealieModel):
|
||||||
id: UUID
|
id: UUID4
|
||||||
text: str
|
text: str
|
||||||
|
|
||||||
|
|
||||||
class RecipeCommentOut(RecipeCommentCreate):
|
class RecipeCommentOut(RecipeCommentCreate):
|
||||||
id: UUID
|
id: UUID4
|
||||||
recipe_id: UUID4
|
recipe_id: UUID4
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
update_at: datetime
|
update_at: datetime
|
||||||
|
Loading…
x
Reference in New Issue
Block a user