tina4ruby 3.13.81 → 3.13.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -2
- data/README.md +4 -4
- data/lib/tina4/background.rb +21 -2
- data/lib/tina4/cli.rb +141 -14
- data/lib/tina4/mqtt.rb +800 -0
- data/lib/tina4/mqtt_message.rb +78 -0
- data/lib/tina4/public/css/tina4.css +56 -130
- data/lib/tina4/public/css/tina4.min.css +1 -1
- data/lib/tina4/rack_app.rb +13 -0
- data/lib/tina4/router.rb +13 -2
- data/lib/tina4/scss/tina4css/_pagination.scss +63 -0
- data/lib/tina4/scss/tina4css/_utilities.scss +11 -0
- data/lib/tina4/scss/tina4css/tina4.scss +1 -0
- data/lib/tina4/scss_compiler.rb +48 -3
- data/lib/tina4/test_client.rb +39 -52
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4.rb +16 -1
- metadata +6 -3
- /data/lib/tina4/{sql_translation.rb → sql_translator.rb} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
*,*::before,*::after{box-sizing:border-box}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;scroll-behavior:smooth}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0.5rem;font-weight:500;line-height:1.25}p{margin-top:0;margin-bottom:1rem}ol,ul{padding-left:2rem;margin-top:0;margin-bottom:1rem}a{color:#4a90d9;text-decoration:none}a:hover{color:#256ab1;text-decoration:underline}img,svg{max-width:100%;height:auto;vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button;cursor:pointer}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid rgba(0,0,0,0.1);opacity:0.25}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}[hidden]{display:none !important}h1{font-size:2.25rem}h2{font-size:1.875rem}h3{font-size:1.5rem}h4{font-size:1.25rem}h5{font-size:1.125rem}h6{font-size:1rem}small,.small{font-size:0.875em}mark,.mark{padding:0.2em;background-color:rgba(255,193,7,0.3)}blockquote{margin:0 0 1rem;padding:0.5rem 1rem;border-left:0.25rem solid rgba(0,0,0,0.1);font-size:1.125rem}code{font-size:0.875em;color:#dc3545;word-wrap:break-word}pre{display:block;padding:1rem;font-size:0.875em;color:#212529;background-color:#f8f9fa;border-radius:.25rem}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:0.5rem}.container,.container-fluid{width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container{max-width:540px}}@media (min-width: 768px){.container{max-width:720px}}@media (min-width: 992px){.container{max-width:960px}}@media (min-width: 1200px){.container{max-width:1140px}}@media (min-width: 1400px){.container{max-width:1320px}}.row{display:flex;flex-wrap:wrap;margin-right:calc($grid-gutter / -2);margin-left:calc($grid-gutter / -2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc($grid-gutter / 2);padding-left:calc($grid-gutter / 2)}.col{flex:1 0 0%}.col-1{flex:0 0 auto;width:8.33333%}.col-2{flex:0 0 auto;width:16.66667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333%}.col-5{flex:0 0 auto;width:41.66667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333%}.col-8{flex:0 0 auto;width:66.66667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333%}.col-11{flex:0 0 auto;width:91.66667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333%}.offset-2{margin-left:16.66667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333%}.offset-0{margin-left:0}@media (min-width: 576px){.col-sm{flex:1 0 0%}.col-sm-1{flex:0 0 auto;width:8.33333%}.col-sm-2{flex:0 0 auto;width:16.66667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333%}.col-sm-5{flex:0 0 auto;width:41.66667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333%}.col-sm-8{flex:0 0 auto;width:66.66667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333%}.col-sm-11{flex:0 0 auto;width:91.66667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}}@media (min-width: 768px){.col-md{flex:1 0 0%}.col-md-1{flex:0 0 auto;width:8.33333%}.col-md-2{flex:0 0 auto;width:16.66667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333%}.col-md-5{flex:0 0 auto;width:41.66667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333%}.col-md-8{flex:0 0 auto;width:66.66667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333%}.col-md-11{flex:0 0 auto;width:91.66667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.col-lg-1{flex:0 0 auto;width:8.33333%}.col-lg-2{flex:0 0 auto;width:16.66667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333%}.col-lg-5{flex:0 0 auto;width:41.66667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333%}.col-lg-8{flex:0 0 auto;width:66.66667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333%}.col-lg-11{flex:0 0 auto;width:91.66667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.col-xl-1{flex:0 0 auto;width:8.33333%}.col-xl-2{flex:0 0 auto;width:16.66667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333%}.col-xl-5{flex:0 0 auto;width:41.66667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333%}.col-xl-8{flex:0 0 auto;width:66.66667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333%}.col-xl-11{flex:0 0 auto;width:91.66667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.align-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:1rem;border-radius:.25rem;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;text-decoration:none}.btn:disabled,.btn.disabled{pointer-events:none;opacity:0.65}.btn-primary{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.btn-primary:hover{background-color:#2b7bcf;border-color:#2a76c6}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{background-color:#596167;border-color:#545b62}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{background-color:#208637;border-color:#1e7e34}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{background-color:#c62232;border-color:#bd2130}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{background-color:#dda600;border-color:#d39e00}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{background-color:#128294;border-color:#117a8b}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{background-color:#e0e5e9;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{background-color:#0f1112;border-color:#0a0c0d}.btn-outline-primary{color:#4a90d9;border-color:#4a90d9;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#4a90d9}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d}.btn-outline-success{color:#28a745;border-color:#28a745;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#28a745}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#dc3545}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:transparent}.btn-outline-warning:hover{color:#212529;background-color:#ffc107}.btn-outline-info{color:#17a2b8;border-color:#17a2b8;background-color:transparent}.btn-outline-info:hover{color:#fff;background-color:#17a2b8}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa}.btn-outline-dark{color:#212529;border-color:#212529;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#212529}.btn-sm{padding:0.25rem 0.5rem;font-size:.875rem;border-radius:.25rem}.btn-lg{padding:0.5rem 1rem;font-size:1.125rem;border-radius:.5rem}.btn-block{display:block;width:100%}.form-group{margin-bottom:1rem}.form-label{display:inline-block;margin-bottom:0.5rem;font-weight:700}.form-control{display:block;width:100%;padding:0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-control:focus{color:#212529;background-color:#fff;border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#f8f9fa;opacity:1}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}.form-select{display:block;width:100%;padding:0.375rem 2.25rem 0.375rem 0.75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;transition:border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;appearance:none}.form-select:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:0.125rem}.form-check-input{float:left;width:1em;height:1em;margin-left:-1.5em;margin-top:0.25em;appearance:none;background-color:#fff;border:1px solid rgba(0,0,0,0.25)}.form-check-input[type="checkbox"]{border-radius:0.25em}.form-check-input[type="radio"]{border-radius:50%}.form-check-input:checked{background-color:#4a90d9;border-color:#4a90d9}.form-check-input:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.form-check-label{cursor:pointer}.is-valid{border-color:#28a745 !important}.is-valid:focus{box-shadow:0 0 0 0.2rem rgba(40,167,69,0.25) !important}.is-invalid{border-color:#dc3545 !important}.is-invalid:focus{box-shadow:0 0 0 0.2rem rgba(220,53,69,0.25) !important}.valid-feedback,.invalid-feedback{display:none;width:100%;margin-top:0.25rem;font-size:0.875em}.valid-feedback{color:#28a745}.invalid-feedback{color:#dc3545}.is-valid~.valid-feedback,.is-invalid~.invalid-feedback{display:block}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group-text{display:flex;align-items:center;padding:0.375rem 0.75rem;font-size:1rem;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,0.125);border-radius:.5rem}.card-header{padding:0.75rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,0.03);border-bottom:1px solid rgba(0,0,0,0.125)}.card-header:first-child{border-radius:calc($border-radius-lg - 1px) calc($border-radius-lg - 1px) 0 0}.card-body{flex:1 1 auto;padding:1rem}.card-footer{padding:0.75rem 1rem;background-color:rgba(0,0,0,0.03);border-top:1px solid rgba(0,0,0,0.125)}.card-footer:last-child{border-radius:0 0 calc($border-radius-lg - 1px) calc($border-radius-lg - 1px)}.card-title{margin-bottom:0.5rem;font-weight:700}.card-text:last-child{margin-bottom:0}.card-img-top{width:100%;border-top-left-radius:calc($border-radius-lg - 1px);border-top-right-radius:calc($border-radius-lg - 1px)}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:0.5rem 1rem;color:#4a90d9;text-decoration:none;transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out}.nav-link:hover,.nav-link:focus{color:#256ab1}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-link.active{color:#4a90d9;font-weight:700}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:0.5rem 1rem}.navbar-brand{padding-top:0.3125rem;padding-bottom:0.3125rem;margin-right:1rem;font-size:1.25rem;font-weight:700;color:inherit;text-decoration:none;white-space:nowrap}.navbar-brand:hover{color:inherit;opacity:0.8}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0.5rem;padding-left:0.5rem}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.125rem;line-height:1;background-color:transparent;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;cursor:pointer}.navbar-toggler:focus{outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center;display:none}.navbar-collapse.show{display:flex}@media (min-width: 576px){.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width: 768px){.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width: 1200px){.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width: 1400px){.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-nav{flex-direction:row}.navbar-collapse{display:flex;flex-basis:auto}.navbar-toggler{display:none}}.navbar-dark{color:#fff;background-color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .nav-link{color:rgba(255,255,255,0.75)}.navbar-dark .nav-link:hover,.navbar-dark .nav-link.active{color:#fff}.navbar-light{background-color:#f8f9fa}.navbar-light .nav-link{color:rgba(0,0,0,0.55)}.navbar-light .nav-link:hover,.navbar-light .nav-link.active{color:rgba(0,0,0,0.9)}.breadcrumb{display:flex;flex-wrap:wrap;padding:0.5rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f8f9fa;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:0.5rem;padding-left:0.5rem;color:#6c757d;content:"/"}.breadcrumb-item.active{color:#6c757d}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal.show{display:block}.modal-dialog{position:relative;width:auto;margin:1.75rem auto;max-width:500px}.modal-sm{max-width:300px}.modal-lg{max-width:800px}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.2);border-radius:.5rem;box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15);outline:0}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid rgba(0,0,0,0.1)}.modal-title{margin-bottom:0;font-weight:700}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:0.75rem;border-top:1px solid rgba(0,0,0,0.1);gap:0.5rem}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:rgba(0,0,0,0.5);display:none}.modal-backdrop.show{display:block}.alert{position:relative;padding:0.75rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-primary{color:#1c5187;background-color:#deeaf8;border-color:#b3d1ef}.alert-secondary{color:#313539;background-color:#caced1;border-color:#afb5ba}.alert-success{color:#0f401b;background-color:#9be7ac;border-color:#71dd8a}.alert-danger{color:#7c151f;background-color:#f6cdd1;border-color:#efa2a9}.alert-warning{color:#876500;background-color:#ffeeba;border-color:#ffe187}.alert-info{color:#093e47;background-color:#90e4f1;border-color:#63d9ec}.alert-light{color:#aeb9c5;background-color:#fff;border-color:#fff}.alert-dark{color:#000;background-color:#717e8c;border-color:#5a6570}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;padding:0.9375rem 1rem;background:transparent;border:0;cursor:pointer;color:inherit;opacity:0.5}.alert-dismissible .btn-close:hover{opacity:0.75}.table{width:100%;margin-bottom:1rem;vertical-align:top;border-color:rgba(0,0,0,0.1)}.table>:not(caption)>*>*{padding:0.5rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:inherit}.table>thead{vertical-align:bottom;border-bottom:2px solid currentColor}.table>tbody>tr:last-child>*{border-bottom-color:transparent}.table-sm>:not(caption)>*>*{padding:0.25rem}.table-bordered{border:1px solid rgba(0,0,0,0.1)}.table-bordered>:not(caption)>*>*{border-width:1px;border-style:solid;border-color:inherit}.table-striped>tbody>tr:nth-of-type(odd)>*{background-color:rgba(0,0,0,0.02)}.table-hover>tbody>tr:hover>*{background-color:rgba(0,0,0,0.04)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.badge{display:inline-block;padding:0.25em 0.65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:50rem}.badge-primary{color:#fff;background-color:#4a90d9}.badge-secondary{color:#fff;background-color:#6c757d}.badge-success{color:#fff;background-color:#28a745}.badge-danger{color:#fff;background-color:#dc3545}.badge-warning{color:#212529;background-color:#ffc107}.badge-info{color:#fff;background-color:#17a2b8}.badge-light{color:#212529;background-color:#f8f9fa}.badge-dark{color:#fff;background-color:#212529}.pagination{display:flex;flex-wrap:wrap;padding-left:0;list-style:none;gap:0.25rem}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:rgba(0,0,0,0.1)}.page-item.active .page-link{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.page-link{display:block;padding:0.375rem 0.75rem;color:#4a90d9;background-color:#fff;border:1px solid rgba(0,0,0,0.15);border-radius:.25rem;text-decoration:none;line-height:1.25;transition:color 0.15s, background-color 0.15s, border-color 0.15s}.page-link:hover{color:#2a76c6;background-color:rgba(74,144,217,0.08);border-color:rgba(0,0,0,0.2)}.page-link:focus{outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.crud-img-preview{max-height:200px;max-width:100%;border-radius:.25rem;object-fit:cover}.crud-img-preview-sm{max-height:180px;max-width:100%;border-radius:.25rem;object-fit:cover}.d-none{display:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-grid{display:grid !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}}@media (min-width: 768px){.d-md-none{display:none !important}.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}}@media (min-width: 992px){.d-lg-none{display:none !important}.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.text-start{text-align:left !important}.text-center{text-align:center !important}.text-end{text-align:right !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fw-bold{font-weight:700 !important}.fw-normal{font-weight:400 !important}.fw-light{font-weight:300 !important}.fs-1{font-size:2.25rem !important}.fs-2{font-size:1.875rem !important}.fs-3{font-size:1.5rem !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1rem !important}.fs-6{font-size:.875rem !important}.text-primary{color:#4a90d9 !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-danger{color:#dc3545 !important}.text-warning{color:#ffc107 !important}.text-info{color:#17a2b8 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-muted{color:#6c757d !important}.text-white{color:#fff !important}.bg-primary{background-color:#4a90d9 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#28a745 !important}.bg-danger{background-color:#dc3545 !important}.bg-warning{background-color:#ffc107 !important}.bg-info{background-color:#17a2b8 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-white{background-color:#fff !important}.border{border:1px solid rgba(0,0,0,0.1) !important}.border-0{border:0 !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.w-25{width:25% !important}.h-25{height:25% !important}.w-50{width:50% !important}.h-50{height:50% !important}.w-75{width:75% !important}.h-75{height:75% !important}.w-100{width:100% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.position-static{position:static !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.overflow-hidden{overflow:hidden !important}.overflow-auto{overflow:auto !important}.shadow{box-shadow:0 0.5rem 1rem rgba(0,0,0,0.15) !important}.shadow-sm{box-shadow:0 0.125rem 0.25rem rgba(0,0,0,0.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,0.175) !important}.shadow-none{box-shadow:none !important}.collapse{display:none}.collapse.show{display:block}.fade{opacity:0;transition:opacity 0.15s linear}.fade.show{opacity:1}.clearfix::after{display:block;clear:both;content:""}.cursor-pointer{cursor:pointer !important}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:0.25em;color:#000;background:transparent;border:0;opacity:0.5;cursor:pointer;font-size:1.25rem;line-height:1}.btn-close:hover{opacity:0.75}.btn-close:focus{opacity:1;outline:0;box-shadow:0 0 0 0.2rem rgba(74,144,217,0.25)}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:0.25rem;background-color:#fff;border:1px solid rgba(0,0,0,0.1);border-radius:.25rem;max-width:100%;height:auto}.border-top{border-top:1px solid rgba(0,0,0,0.1) !important}.border-bottom{border-bottom:1px solid rgba(0,0,0,0.1) !important}.align-middle{vertical-align:middle !important}.align-top{vertical-align:top !important}.align-bottom{vertical-align:bottom !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}
|
|
1
|
+
*,*::before,*::after{box-sizing:border-box}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;scroll-behavior:smooth}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.25}p{margin-top:0;margin-bottom:1rem}ol,ul{padding-left:2rem;margin-top:0;margin-bottom:1rem}a{color:#4a90d9;text-decoration:none}a:hover{color:#256ab1;text-decoration:underline}img,svg{max-width:100%;height:auto;vertical-align:middle}table{border-collapse:collapse;caption-side:bottom}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;cursor:pointer}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid rgba(0,0,0,.1);opacity:.25}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}[hidden]{display:none !important}h1{font-size:2.25rem}h2{font-size:1.875rem}h3{font-size:1.5rem}h4{font-size:1.25rem}h5{font-size:1.125rem}h6{font-size:1rem}small,.small{font-size:.875em}mark,.mark{padding:.2em;background-color:rgba(255,193,7,.3)}blockquote{margin:0 0 1rem;padding:.5rem 1rem;border-left:.25rem solid rgba(0,0,0,.1);font-size:1.125rem}code{font-size:.875em;color:#dc3545;word-wrap:break-word}pre{display:block;padding:1rem;font-size:.875em;color:#212529;background-color:#f8f9fa;border-radius:.25rem}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.container,.container-fluid{width:100%;padding-right:.75rem;padding-left:.75rem;margin-right:auto;margin-left:auto}@media(min-width: 576px){.container{max-width:540px}}@media(min-width: 768px){.container{max-width:720px}}@media(min-width: 992px){.container{max-width:960px}}@media(min-width: 1200px){.container{max-width:1140px}}@media(min-width: 1400px){.container{max-width:1320px}}.row{display:flex;flex-wrap:wrap;margin-right:-0.75rem;margin-left:-0.75rem}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:.75rem;padding-left:.75rem}.col{flex:1 0 0%}.col-1{flex:0 0 auto;width:8.3333333333%}.col-2{flex:0 0 auto;width:16.6666666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.3333333333%}.col-5{flex:0 0 auto;width:41.6666666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.3333333333%}.col-8{flex:0 0 auto;width:66.6666666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.3333333333%}.col-11{flex:0 0 auto;width:91.6666666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-0{margin-left:0}@media(min-width: 576px){.col-sm{flex:1 0 0%}.col-sm-1{flex:0 0 auto;width:8.3333333333%}.col-sm-2{flex:0 0 auto;width:16.6666666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.3333333333%}.col-sm-5{flex:0 0 auto;width:41.6666666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.3333333333%}.col-sm-8{flex:0 0 auto;width:66.6666666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.3333333333%}.col-sm-11{flex:0 0 auto;width:91.6666666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}}@media(min-width: 768px){.col-md{flex:1 0 0%}.col-md-1{flex:0 0 auto;width:8.3333333333%}.col-md-2{flex:0 0 auto;width:16.6666666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.3333333333%}.col-md-5{flex:0 0 auto;width:41.6666666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.3333333333%}.col-md-8{flex:0 0 auto;width:66.6666666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.3333333333%}.col-md-11{flex:0 0 auto;width:91.6666666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}}@media(min-width: 992px){.col-lg{flex:1 0 0%}.col-lg-1{flex:0 0 auto;width:8.3333333333%}.col-lg-2{flex:0 0 auto;width:16.6666666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.3333333333%}.col-lg-5{flex:0 0 auto;width:41.6666666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.3333333333%}.col-lg-8{flex:0 0 auto;width:66.6666666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.3333333333%}.col-lg-11{flex:0 0 auto;width:91.6666666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}}@media(min-width: 1200px){.col-xl{flex:1 0 0%}.col-xl-1{flex:0 0 auto;width:8.3333333333%}.col-xl-2{flex:0 0 auto;width:16.6666666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.3333333333%}.col-xl-5{flex:0 0 auto;width:41.6666666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.3333333333%}.col-xl-8{flex:0 0 auto;width:66.6666666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.3333333333%}.col-xl-11{flex:0 0 auto;width:91.6666666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.align-start{align-items:flex-start}.align-center{align-items:center}.align-end{align-items:flex-end}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;text-decoration:none}.btn:disabled,.btn.disabled{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.btn-primary:hover{background-color:#2b7bcf;border-color:#2a76c6}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{background-color:#596167;border-color:#545b62}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{background-color:#208637;border-color:#1e7e34}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{background-color:#c62232;border-color:#bd2130}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{background-color:#dda600;border-color:#d39e00}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{background-color:#128294;border-color:#117a8b}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{background-color:#e0e5e9;border-color:#dae0e5}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{background-color:#0f1112;border-color:#0a0c0d}.btn-outline-primary{color:#4a90d9;border-color:#4a90d9;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#4a90d9}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d}.btn-outline-success{color:#28a745;border-color:#28a745;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#28a745}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#dc3545}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#212529;background-color:#ffc107}.btn-outline-info{color:#17a2b8;border-color:#17a2b8;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#fff;background-color:#17a2b8}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa}.btn-outline-dark{color:#212529;border-color:#212529;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#212529}.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.25rem}.btn-lg{padding:.5rem 1rem;font-size:1.125rem;border-radius:.5rem}.btn-block{display:block;width:100%}.form-group{margin-bottom:1rem}.form-label{display:inline-block;margin-bottom:.5rem;font-weight:700}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}.form-control:focus{color:#212529;background-color:#fff;border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled{background-color:#f8f9fa;opacity:1}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}.form-select:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check-input{float:left;width:1em;height:1em;margin-left:-1.5em;margin-top:.25em;appearance:none;background-color:#fff;border:1px solid rgba(0,0,0,.25)}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:checked{background-color:#4a90d9;border-color:#4a90d9}.form-check-input:focus{border-color:#b3d1ef;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.form-check-label{cursor:pointer}.is-valid{border-color:#28a745 !important}.is-valid:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.25) !important}.is-invalid{border-color:#dc3545 !important}.is-invalid:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.25) !important}.valid-feedback,.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em}.valid-feedback{color:#28a745}.invalid-feedback{color:#dc3545}.is-valid~.valid-feedback,.is-invalid~.invalid-feedback{display:block}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #ced4da;border-radius:.25rem}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.5rem}.card-header{padding:.75rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.5rem - 1px) calc(.5rem - 1px) 0 0}.card-body{flex:1 1 auto;padding:1rem}.card-footer{padding:.75rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.5rem - 1px) calc(.5rem - 1px)}.card-title{margin-bottom:.5rem;font-weight:700}.card-text:last-child{margin-bottom:0}.card-img-top{width:100%;border-top-left-radius:calc(.5rem - 1px);border-top-right-radius:calc(.5rem - 1px)}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#4a90d9;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out}.nav-link:hover,.nav-link:focus{color:#256ab1}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-link.active{color:#4a90d9;font-weight:700}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:.5rem 1rem}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;font-weight:700;color:inherit;text-decoration:none;white-space:nowrap}.navbar-brand:hover{color:inherit;opacity:.8}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-toggler{padding:.25rem .75rem;font-size:1.125rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;cursor:pointer}.navbar-toggler:focus{outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center;display:none}.navbar-collapse.show{display:flex}@media(min-width: 576px){.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media(min-width: 768px){.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media(min-width: 992px){.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media(min-width: 1200px){.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media(min-width: 1400px){.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-collapse{display:flex;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}@media(min-width: 992px){.navbar-nav{flex-direction:row}.navbar-collapse{display:flex;flex-basis:auto}.navbar-toggler{display:none}}.navbar-dark{color:#fff;background-color:#212529}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .nav-link{color:rgba(255,255,255,.75)}.navbar-dark .nav-link:hover,.navbar-dark .nav-link.active{color:#fff}.navbar-light{background-color:#f8f9fa}.navbar-light .nav-link{color:rgba(0,0,0,.55)}.navbar-light .nav-link:hover,.navbar-light .nav-link.active{color:rgba(0,0,0,.9)}.breadcrumb{display:flex;flex-wrap:wrap;padding:.5rem 1rem;margin-bottom:1rem;list-style:none;background-color:#f8f9fa;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item.active{color:#6c757d}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal.show{display:block}.modal-dialog{position:relative;width:auto;margin:1.75rem auto;max-width:500px}.modal-sm{max-width:300px}.modal-lg{max-width:800px}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.5rem;box-shadow:0 .5rem 1rem rgba(0,0,0,.15);outline:0}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem;border-bottom:1px solid rgba(0,0,0,.1)}.modal-title{margin-bottom:0;font-weight:700}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid rgba(0,0,0,.1);gap:.5rem}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:rgba(0,0,0,.5);display:none}.modal-backdrop.show{display:block}.alert{position:relative;padding:.75rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-primary{color:#1c5187;background-color:#deeaf8;border-color:#b3d1ef}.alert-secondary{color:#313539;background-color:#caced1;border-color:#afb5ba}.alert-success{color:#0f401b;background-color:#9be7ac;border-color:#71dd8a}.alert-danger{color:#7c151f;background-color:#f6cdd1;border-color:#efa2a9}.alert-warning{color:#876500;background-color:#ffeeba;border-color:#ffe187}.alert-info{color:#093e47;background-color:#90e4f1;border-color:#63d9ec}.alert-light{color:#aeb9c5;background-color:#fff;border-color:#fff}.alert-dark{color:#000;background-color:#717e8c;border-color:#5a6570}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;padding:.9375rem 1rem;background:rgba(0,0,0,0);border:0;cursor:pointer;color:inherit;opacity:.5}.alert-dismissible .btn-close:hover{opacity:.75}.table{width:100%;margin-bottom:1rem;vertical-align:top;border-color:rgba(0,0,0,.1)}.table>:not(caption)>*>*{padding:.5rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:inherit}.table>thead{vertical-align:bottom;border-bottom:2px solid currentColor}.table>tbody>tr:last-child>*{border-bottom-color:rgba(0,0,0,0)}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered{border:1px solid rgba(0,0,0,.1)}.table-bordered>:not(caption)>*>*{border-width:1px;border-style:solid;border-color:inherit}.table-striped>tbody>tr:nth-of-type(odd)>*{background-color:rgba(0,0,0,.02)}.table-hover>tbody>tr:hover>*{background-color:rgba(0,0,0,.04)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}.badge{display:inline-block;padding:.25em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:50rem}.badge-primary{color:#fff;background-color:#4a90d9}.badge-secondary{color:#fff;background-color:#6c757d}.badge-success{color:#fff;background-color:#28a745}.badge-danger{color:#fff;background-color:#dc3545}.badge-warning{color:#212529;background-color:#ffc107}.badge-info{color:#fff;background-color:#17a2b8}.badge-light{color:#212529;background-color:#f8f9fa}.badge-dark{color:#fff;background-color:#212529}.pagination{display:flex;flex-wrap:wrap;padding-left:0;list-style:none;gap:.25rem}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:rgba(0,0,0,.1)}.page-item.active .page-link{color:#fff;background-color:#4a90d9;border-color:#4a90d9}.page-link{display:block;padding:.375rem .75rem;color:#4a90d9;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;text-decoration:none;line-height:1.25;transition:color .15s,background-color .15s,border-color .15s}.page-link:hover{color:#2a76c6;background-color:rgba(74,144,217,.08);border-color:rgba(0,0,0,.2)}.page-link:focus{outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.crud-img-preview{max-height:200px;max-width:100%;border-radius:.25rem;object-fit:cover}.crud-img-preview-sm{max-height:180px;max-width:100%;border-radius:.25rem;object-fit:cover}.d-none{display:none !important}.d-block{display:block !important}.d-flex{display:flex !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-grid{display:grid !important}@media(min-width: 576px){.d-sm-none{display:none !important}.d-sm-block{display:block !important}.d-sm-flex{display:flex !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}}@media(min-width: 768px){.d-md-none{display:none !important}.d-md-block{display:block !important}.d-md-flex{display:flex !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}}@media(min-width: 992px){.d-lg-none{display:none !important}.d-lg-block{display:block !important}.d-lg-flex{display:flex !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.mx-0{margin-left:0 !important;margin-right:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.mx-1{margin-left:.25rem !important;margin-right:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.mx-2{margin-left:.5rem !important;margin-right:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.mx-3{margin-left:1rem !important;margin-right:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.mx-4{margin-left:1.5rem !important;margin-right:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.mx-5{margin-left:3rem !important;margin-right:3rem !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.px-0{padding-left:0 !important;padding-right:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.px-1{padding-left:.25rem !important;padding-right:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.px-2{padding-left:.5rem !important;padding-right:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.px-3{padding-left:1rem !important;padding-right:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.px-4{padding-left:1.5rem !important;padding-right:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.px-5{padding-left:3rem !important;padding-right:3rem !important}.mx-auto{margin-left:auto !important;margin-right:auto !important}.text-start{text-align:left !important}.text-center{text-align:center !important}.text-end{text-align:right !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-nowrap{white-space:nowrap !important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fw-bold{font-weight:700 !important}.fw-normal{font-weight:400 !important}.fw-light{font-weight:300 !important}.fs-1{font-size:2.25rem !important}.fs-2{font-size:1.875rem !important}.fs-3{font-size:1.5rem !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1rem !important}.fs-6{font-size:.875rem !important}.text-primary{color:#4a90d9 !important}.text-secondary{color:#6c757d !important}.text-success{color:#28a745 !important}.text-danger{color:#dc3545 !important}.text-warning{color:#ffc107 !important}.text-info{color:#17a2b8 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-muted{color:#6c757d !important}.text-white{color:#fff !important}.bg-primary{background-color:#4a90d9 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#28a745 !important}.bg-danger{background-color:#dc3545 !important}.bg-warning{background-color:#ffc107 !important}.bg-info{background-color:#17a2b8 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-white{background-color:#fff !important}.border{border:1px solid rgba(0,0,0,.1) !important}.border-0{border:0 !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.w-25{width:25% !important}.h-25{height:25% !important}.w-50{width:50% !important}.h-50{height:50% !important}.w-75{width:75% !important}.h-75{height:75% !important}.w-100{width:100% !important}.h-100{height:100% !important}.mw-100{max-width:100% !important}.mh-100{max-height:100% !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.position-static{position:static !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.overflow-hidden{overflow:hidden !important}.overflow-auto{overflow:auto !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.collapse{display:none}.collapse.show{display:block}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.clearfix::after{display:block;clear:both;content:""}.cursor-pointer{cursor:pointer !important}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em;color:#000;background:rgba(0,0,0,0);border:0;opacity:.5;cursor:pointer;font-size:1.25rem;line-height:1}.btn-close:hover{opacity:.75}.btn-close:focus{opacity:1;outline:0;box-shadow:0 0 0 .2rem rgba(74,144,217,.25)}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid rgba(0,0,0,.1);border-radius:.25rem;max-width:100%;height:auto}.border-top{border-top:1px solid rgba(0,0,0,.1) !important}.border-bottom{border-bottom:1px solid rgba(0,0,0,.1) !important}.align-middle{vertical-align:middle !important}.align-top{vertical-align:top !important}.align-bottom{vertical-align:bottom !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.visually-hidden{position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -19,6 +19,16 @@ module Tina4
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
class RackApp
|
|
22
|
+
class << self
|
|
23
|
+
# The process-wide RackApp — the app actually serving traffic. Set by
|
|
24
|
+
# #initialize (last one wins, the same convention as
|
|
25
|
+
# Tina4::WebSocket.current). Tina4::TestClient defaults to this so an
|
|
26
|
+
# in-process test request runs through the SAME app object a live request
|
|
27
|
+
# does, instead of building a second one with a different static root and
|
|
28
|
+
# clobbering the shared WebSocket engine. (feature-recount D6)
|
|
29
|
+
attr_accessor :current
|
|
30
|
+
end
|
|
31
|
+
|
|
22
32
|
STATIC_DIRS = %w[public src/public src/assets assets].freeze
|
|
23
33
|
|
|
24
34
|
# CORS is now handled by Tina4::CorsMiddleware
|
|
@@ -46,6 +56,9 @@ module Tina4
|
|
|
46
56
|
# manager. Without this the handshake never matches a route and falls
|
|
47
57
|
# through to 404, silently degrading the whole reloader to polling.
|
|
48
58
|
RackApp.register_dev_reload_ws if dev_mode?
|
|
59
|
+
|
|
60
|
+
# Publish as the process-wide app (see RackApp.current).
|
|
61
|
+
RackApp.current = self
|
|
49
62
|
end
|
|
50
63
|
|
|
51
64
|
# WebSocket handler for the dev-reload channel (/__dev_reload).
|
data/lib/tina4/router.rb
CHANGED
|
@@ -615,8 +615,19 @@ module Tina4
|
|
|
615
615
|
@last_routes_dir = nil
|
|
616
616
|
end
|
|
617
617
|
|
|
618
|
-
# Write a .broken sentinel
|
|
619
|
-
#
|
|
618
|
+
# Write a .broken sentinel to data/.broken/ so an auto-discover failure
|
|
619
|
+
# leaves a durable on-disk artifact instead of being swallowed into a log
|
|
620
|
+
# line.
|
|
621
|
+
#
|
|
622
|
+
# Ruby only WRITES these files today — nothing under lib/ reads them back.
|
|
623
|
+
# lib/tina4/health.rb contains no `broken` reference, and
|
|
624
|
+
# GET /__dev/api/broken serves the in-memory Tina4::DevAdmin::ErrorTracker
|
|
625
|
+
# (its own JSON store under Dir.tmpdir, dev_admin.rb:127-132), not this
|
|
626
|
+
# directory. Python's /health DOES glob data/.broken and answer 503 with
|
|
627
|
+
# `errors` + `latest_error` (tina4-python/tina4_python/core/server.py:296-320);
|
|
628
|
+
# mirroring that read side in Ruby is an OPEN parity gap. This comment used
|
|
629
|
+
# to claim "/health and the dev dashboard surface" it, which was false in
|
|
630
|
+
# both halves. (feature-recount D12)
|
|
620
631
|
def record_broken_route_import(file, error)
|
|
621
632
|
broken_dir = File.join(Dir.pwd, "data", ".broken")
|
|
622
633
|
FileUtils.mkdir_p(broken_dir) unless Dir.exist?(broken_dir)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Tina4 CSS Framework - Pagination
|
|
2
|
+
// ----------------------------------
|
|
3
|
+
|
|
4
|
+
.pagination {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-wrap: wrap;
|
|
7
|
+
padding-left: 0;
|
|
8
|
+
list-style: none;
|
|
9
|
+
gap: 0.25rem;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.page-item {
|
|
13
|
+
&.disabled .page-link {
|
|
14
|
+
color: $muted;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
background-color: $body-bg;
|
|
17
|
+
border-color: rgba($black, 0.1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.active .page-link {
|
|
21
|
+
color: $white;
|
|
22
|
+
background-color: $primary;
|
|
23
|
+
border-color: $primary;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.page-link {
|
|
28
|
+
display: block;
|
|
29
|
+
padding: 0.375rem 0.75rem;
|
|
30
|
+
color: $primary;
|
|
31
|
+
background-color: $body-bg;
|
|
32
|
+
border: 1px solid rgba($black, 0.15);
|
|
33
|
+
border-radius: $border-radius;
|
|
34
|
+
text-decoration: none;
|
|
35
|
+
line-height: 1.25;
|
|
36
|
+
transition: color 0.15s, background-color 0.15s, border-color 0.15s;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
color: darken($primary, 10%);
|
|
40
|
+
background-color: rgba($primary, 0.08);
|
|
41
|
+
border-color: rgba($black, 0.2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&:focus {
|
|
45
|
+
outline: 0;
|
|
46
|
+
box-shadow: 0 0 0 0.2rem rgba($primary, 0.25);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// CRUD component image preview
|
|
51
|
+
.crud-img-preview {
|
|
52
|
+
max-height: 200px;
|
|
53
|
+
max-width: 100%;
|
|
54
|
+
border-radius: $border-radius;
|
|
55
|
+
object-fit: cover;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.crud-img-preview-sm {
|
|
59
|
+
max-height: 180px;
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
border-radius: $border-radius;
|
|
62
|
+
object-fit: cover;
|
|
63
|
+
}
|
|
@@ -61,6 +61,17 @@ $spacing-dirs: (
|
|
|
61
61
|
.#{$abbr}#{$dir}-#{$key} { #{$prop}#{$suffix}: $val !important; }
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
// Axis shortcuts: py-* (top+bottom), px-* (left+right)
|
|
65
|
+
@each $key, $val in $spacers {
|
|
66
|
+
.#{$abbr}y-#{$key} {
|
|
67
|
+
#{$prop}-top: $val !important;
|
|
68
|
+
#{$prop}-bottom: $val !important;
|
|
69
|
+
}
|
|
70
|
+
.#{$abbr}x-#{$key} {
|
|
71
|
+
#{$prop}-left: $val !important;
|
|
72
|
+
#{$prop}-right: $val !important;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
.mx-auto { margin-left: auto !important; margin-right: auto !important; }
|
data/lib/tina4/scss_compiler.rb
CHANGED
|
@@ -6,6 +6,15 @@ module Tina4
|
|
|
6
6
|
SCSS_DIRS = %w[src/scss scss src/styles styles].freeze
|
|
7
7
|
CSS_OUTPUT = "src/public/css"
|
|
8
8
|
|
|
9
|
+
# Flags that may trail a variable declaration's value. `!default` means
|
|
10
|
+
# "assign only if this variable is not already set" -- the flag that makes a
|
|
11
|
+
# variable themeable. `!global` is the scope flag. Both are compiler
|
|
12
|
+
# directives: they are consumed at the declaration and must never reach the
|
|
13
|
+
# CSS, because `padding: 1.5rem !default` is invalid CSS and browsers drop
|
|
14
|
+
# the whole declaration. Sass flag names are case-SENSITIVE (`!DEFAULT` is an
|
|
15
|
+
# error in Dart Sass), so the match is deliberately case-sensitive.
|
|
16
|
+
VARIABLE_FLAG = /\s*!(default|global)\s*\z/
|
|
17
|
+
|
|
9
18
|
# Module-level state for import paths and variables
|
|
10
19
|
@import_paths = []
|
|
11
20
|
@variables = {}
|
|
@@ -92,14 +101,50 @@ module Tina4
|
|
|
92
101
|
|
|
93
102
|
private
|
|
94
103
|
|
|
104
|
+
# Split trailing !default / !global flags off a variable declaration value.
|
|
105
|
+
# Returns [value_without_flags, declares_default].
|
|
106
|
+
#
|
|
107
|
+
# Only ever called on the value of a `$name: value;` declaration, so a
|
|
108
|
+
# literal !default anywhere else -- inside a quoted string
|
|
109
|
+
# (`content: "x !default y"`) or a function argument -- is left untouched,
|
|
110
|
+
# exactly as Dart Sass leaves it. A blanket strip would corrupt real string
|
|
111
|
+
# content, and would silently turn `rgba(#000 !default, 0.1)` (a syntax
|
|
112
|
+
# error in Dart Sass) into valid-looking CSS that Sass would never emit.
|
|
113
|
+
#
|
|
114
|
+
# NOTE: this runs a regex, so it clobbers the caller's match globals --
|
|
115
|
+
# callers must capture their own groups first.
|
|
116
|
+
def strip_variable_flags(value)
|
|
117
|
+
declares_default = false
|
|
118
|
+
loop do
|
|
119
|
+
match = VARIABLE_FLAG.match(value)
|
|
120
|
+
break unless match
|
|
121
|
+
|
|
122
|
+
declares_default = true if match[1] == "default"
|
|
123
|
+
value = value[0, match.begin(0)]
|
|
124
|
+
end
|
|
125
|
+
[value.strip, declares_default]
|
|
126
|
+
end
|
|
127
|
+
|
|
95
128
|
def basic_compile(content, base_dir)
|
|
96
129
|
# Handle @import
|
|
97
130
|
content = process_imports(content, base_dir)
|
|
98
131
|
|
|
99
|
-
# Handle variables
|
|
132
|
+
# Handle variables. The /m flag makes the value span newlines, matching
|
|
133
|
+
# the Python master's `[^;]+`: without it a MULTI-LINE declaration (a map
|
|
134
|
+
# literal with its closing `) !default;` on its own line -- exactly what
|
|
135
|
+
# tina4-css's _variables.scss uses) was never extracted, and the raw SCSS
|
|
136
|
+
# was dumped verbatim into the CSS.
|
|
100
137
|
variables = {}
|
|
101
|
-
content = content.gsub(/\$([a-zA-Z_][\w-]*)\s*:\s*(.+?);/) do
|
|
102
|
-
|
|
138
|
+
content = content.gsub(/\$([a-zA-Z_][\w-]*)\s*:\s*(.+?);/m) do
|
|
139
|
+
# Capture BOTH groups before calling strip_variable_flags -- it runs a
|
|
140
|
+
# regex, which clobbers the match globals this block relies on.
|
|
141
|
+
name = Regexp.last_match(1)
|
|
142
|
+
raw = Regexp.last_match(2).strip
|
|
143
|
+
value, declares_default = strip_variable_flags(raw)
|
|
144
|
+
# !default must not overwrite a value that is already set.
|
|
145
|
+
next "" if declares_default && variables.fetch(name, "null") != "null"
|
|
146
|
+
|
|
147
|
+
variables[name] = value
|
|
103
148
|
""
|
|
104
149
|
end
|
|
105
150
|
|
data/lib/tina4/test_client.rb
CHANGED
|
@@ -47,6 +47,23 @@ module Tina4
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
class TestClient
|
|
50
|
+
# +app+ — the Rack app every request is dispatched through. Defaults to the
|
|
51
|
+
# process-wide Tina4::RackApp (RackApp.current): inside a running server that
|
|
52
|
+
# IS the app serving traffic, so an in-process test request and a live request
|
|
53
|
+
# go through the same object. With no app yet (a bare spec process) one is
|
|
54
|
+
# built lazily, rooted at +root_dir+ / Tina4.root_dir / Dir.pwd.
|
|
55
|
+
#
|
|
56
|
+
# Mirrors Node's `new TestClient(router?)` optional-collaborator constructor.
|
|
57
|
+
def initialize(app = nil, root_dir: nil)
|
|
58
|
+
@app = app
|
|
59
|
+
@root_dir = root_dir
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def app
|
|
63
|
+
@app ||= Tina4::RackApp.current ||
|
|
64
|
+
Tina4::RackApp.new(root_dir: @root_dir || Tina4.root_dir || Dir.pwd)
|
|
65
|
+
end
|
|
66
|
+
|
|
50
67
|
# Send a GET request.
|
|
51
68
|
def get(path, headers: nil)
|
|
52
69
|
request("GET", path, headers: headers)
|
|
@@ -74,7 +91,19 @@ module Tina4
|
|
|
74
91
|
|
|
75
92
|
private
|
|
76
93
|
|
|
77
|
-
# Build a
|
|
94
|
+
# Build a Rack env and dispatch it through the REAL Tina4::RackApp — the same
|
|
95
|
+
# front controller a live HTTP request goes through.
|
|
96
|
+
#
|
|
97
|
+
# This used to call Tina4::Router.match directly and invoke the handler
|
|
98
|
+
# itself, which made everything RackApp does around route matching invisible
|
|
99
|
+
# to tests: global middleware never ran, and static files, /swagger,
|
|
100
|
+
# /swagger/openapi.json, the bundled framework assets, /__dev, /__feedback,
|
|
101
|
+
# CORS preflight, the RFC 9110 OPTIONS/405 Allow responses and HEAD
|
|
102
|
+
# body-stripping all came back as a hand-built 404 {"error":"Not found"} while
|
|
103
|
+
# the live server served them 200. Any spec asserting framework endpoint
|
|
104
|
+
# behaviour through TestClient was asserting nothing. (feature-recount D6 —
|
|
105
|
+
# the same shape as the #PY2 auth fix: the in-process test client must route
|
|
106
|
+
# through the real pipeline, not a re-implementation of it.)
|
|
78
107
|
def request(method, path, json: nil, body: nil, headers: nil)
|
|
79
108
|
# Build raw body
|
|
80
109
|
raw_body = ""
|
|
@@ -99,7 +128,9 @@ module Tina4
|
|
|
99
128
|
"SERVER_PORT" => "7145",
|
|
100
129
|
"HTTP_HOST" => "localhost:7145",
|
|
101
130
|
"REMOTE_ADDR" => "127.0.0.1",
|
|
131
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
|
102
132
|
"rack.input" => StringIO.new(raw_body),
|
|
133
|
+
"rack.errors" => $stderr,
|
|
103
134
|
"rack.url_scheme" => "http"
|
|
104
135
|
}
|
|
105
136
|
|
|
@@ -115,57 +146,13 @@ module Tina4
|
|
|
115
146
|
end
|
|
116
147
|
end
|
|
117
148
|
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
route, path_params = result
|
|
126
|
-
|
|
127
|
-
# Route through the REAL auth gate (parity with the live server). A write
|
|
128
|
-
# to an auth-required route (POST/PUT/PATCH/DELETE by default, or any
|
|
129
|
-
# secured route) with no valid token / API key / formToken 401s here
|
|
130
|
-
# exactly as it would in production. The TestClient used to skip this and
|
|
131
|
-
# run the handler directly, so a green test could hide a live 401 — the
|
|
132
|
-
# verification layer lied. A public route (GET, or a write marked
|
|
133
|
-
# .no_auth) has auth_required false, so this is a no-op. (#PY2 parity)
|
|
134
|
-
unauthorized = Tina4::RackApp.enforce_route_auth(env, route)
|
|
135
|
-
return TestResponse.new(unauthorized) if unauthorized
|
|
136
|
-
|
|
137
|
-
# Create request and response
|
|
138
|
-
req = Tina4::Request.new(env, path_params || {})
|
|
139
|
-
res = Tina4::Response.new
|
|
140
|
-
|
|
141
|
-
# Build handler args (same logic as RackApp.handle_route)
|
|
142
|
-
handler_params = route.handler.parameters.map(&:last)
|
|
143
|
-
route_params = path_params || {}
|
|
144
|
-
args = handler_params.map do |name|
|
|
145
|
-
if route_params.key?(name)
|
|
146
|
-
route_params[name]
|
|
147
|
-
elsif name == :request || name == :req
|
|
148
|
-
req
|
|
149
|
-
else
|
|
150
|
-
res
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Execute handler
|
|
155
|
-
handler_result = args.empty? ? route.handler.call : route.handler.call(*args)
|
|
156
|
-
|
|
157
|
-
# Auto-detect response type
|
|
158
|
-
if handler_result.is_a?(Tina4::Response)
|
|
159
|
-
final = handler_result
|
|
160
|
-
elsif route.respond_to?(:template) && route.template && handler_result.is_a?(Hash)
|
|
161
|
-
html = Tina4::Template.render(route.template, handler_result)
|
|
162
|
-
res.html(html)
|
|
163
|
-
final = res
|
|
164
|
-
else
|
|
165
|
-
final = Tina4::Response.auto_detect(handler_result, res)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
TestResponse.new(final.to_rack)
|
|
149
|
+
# Dispatch through the real front controller. Everything the live server
|
|
150
|
+
# does — the secure-by-default auth gate (#PY2), global + per-route
|
|
151
|
+
# middleware, route matching and handler invocation, template rendering,
|
|
152
|
+
# response auto-detection, static files, /swagger, /__dev, /__feedback, the
|
|
153
|
+
# RFC 9110 OPTIONS/405 responses, HEAD body-stripping, session save and the
|
|
154
|
+
# 500 handler — is now exercised exactly once, in one place.
|
|
155
|
+
TestResponse.new(app.call(env))
|
|
169
156
|
end
|
|
170
157
|
end
|
|
171
158
|
end
|
data/lib/tina4/version.rb
CHANGED
data/lib/tina4.rb
CHANGED
|
@@ -42,7 +42,7 @@ require_relative "tina4/messenger"
|
|
|
42
42
|
require_relative "tina4/dev_mailbox"
|
|
43
43
|
require_relative "tina4/ai"
|
|
44
44
|
require_relative "tina4/cache"
|
|
45
|
-
require_relative "tina4/
|
|
45
|
+
require_relative "tina4/sql_translator"
|
|
46
46
|
require_relative "tina4/cache_backends"
|
|
47
47
|
require_relative "tina4/response_cache"
|
|
48
48
|
require_relative "tina4/html_element"
|
|
@@ -106,10 +106,25 @@ module Tina4
|
|
|
106
106
|
autoload :WebSocketBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
|
|
107
107
|
autoload :RedisBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
|
|
108
108
|
autoload :NATSBackplane, File.expand_path("tina4/websocket_backplane", __dir__)
|
|
109
|
+
# MQTT 3.1.1 — zero-dependency IoT client (socket + pack/unpack only).
|
|
110
|
+
# Autoloaded, not required: an app that never talks to a broker pays nothing.
|
|
111
|
+
autoload :Mqtt, File.expand_path("tina4/mqtt", __dir__)
|
|
112
|
+
autoload :MqttError, File.expand_path("tina4/mqtt", __dir__)
|
|
113
|
+
autoload :MqttTimeoutError, File.expand_path("tina4/mqtt", __dir__)
|
|
114
|
+
autoload :MqttMessage, File.expand_path("tina4/mqtt_message", __dir__)
|
|
109
115
|
autoload :Testing, File.expand_path("tina4/testing", __dir__)
|
|
110
116
|
autoload :ScssCompiler, File.expand_path("tina4/scss_compiler", __dir__)
|
|
111
117
|
autoload :FakeData, File.expand_path("tina4/seeder", __dir__)
|
|
112
118
|
autoload :WSDL, File.expand_path("tina4/wsdl", __dir__)
|
|
119
|
+
# Validator is a counted framework feature and belongs on the main
|
|
120
|
+
# `require "tina4"` surface like every other subsystem — PHP reaches
|
|
121
|
+
# Tina4\Validator through composer's autoloader, Node exports Validator from
|
|
122
|
+
# packages/core/src/index.ts, and Python resolves tina4_python.validator as a
|
|
123
|
+
# submodule with no extra wiring. Ruby was the odd one out: validator.rb was
|
|
124
|
+
# neither required nor autoloaded, so Tina4::Validator raised NameError after a
|
|
125
|
+
# clean boot. Autoloaded (not eagerly required) so it still costs nothing until
|
|
126
|
+
# a route actually validates. (feature-recount D5)
|
|
127
|
+
autoload :Validator, File.expand_path("tina4/validator", __dir__)
|
|
113
128
|
BANNER = <<~'BANNER'
|
|
114
129
|
|
|
115
130
|
______ _ __ __
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.13.
|
|
4
|
+
version: 3.13.82
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rack
|
|
@@ -349,6 +349,8 @@ files:
|
|
|
349
349
|
- lib/tina4/metrics.rb
|
|
350
350
|
- lib/tina4/middleware.rb
|
|
351
351
|
- lib/tina4/migration.rb
|
|
352
|
+
- lib/tina4/mqtt.rb
|
|
353
|
+
- lib/tina4/mqtt_message.rb
|
|
352
354
|
- lib/tina4/orm.rb
|
|
353
355
|
- lib/tina4/plan.rb
|
|
354
356
|
- lib/tina4/project_index.rb
|
|
@@ -396,6 +398,7 @@ files:
|
|
|
396
398
|
- lib/tina4/scss/tina4css/_grid.scss
|
|
397
399
|
- lib/tina4/scss/tina4css/_modals.scss
|
|
398
400
|
- lib/tina4/scss/tina4css/_nav.scss
|
|
401
|
+
- lib/tina4/scss/tina4css/_pagination.scss
|
|
399
402
|
- lib/tina4/scss/tina4css/_reset.scss
|
|
400
403
|
- lib/tina4/scss/tina4css/_tables.scss
|
|
401
404
|
- lib/tina4/scss/tina4css/_typography.scss
|
|
@@ -416,7 +419,7 @@ files:
|
|
|
416
419
|
- lib/tina4/session_handlers/resp_client.rb
|
|
417
420
|
- lib/tina4/session_handlers/valkey_handler.rb
|
|
418
421
|
- lib/tina4/shutdown.rb
|
|
419
|
-
- lib/tina4/
|
|
422
|
+
- lib/tina4/sql_translator.rb
|
|
420
423
|
- lib/tina4/swagger.rb
|
|
421
424
|
- lib/tina4/template.rb
|
|
422
425
|
- lib/tina4/templates/base.twig
|
|
File without changes
|