mission_control-servers 0.2.6 → 0.3.0

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/app/assets/builds/mission_control_servers_application.css +1 -1
  4. data/app/controllers/mission_control/servers/dashboards/project_tables_controller.rb +1 -0
  5. data/app/controllers/mission_control/servers/projects_controller.rb +3 -1
  6. data/app/controllers/mission_control/servers/public_projects_controller.rb +56 -0
  7. data/app/controllers/mission_control/servers/service_settings_controller.rb +27 -0
  8. data/app/helpers/mission_control/servers/service_settings_helper.rb +4 -0
  9. data/app/javascript/mission_control/servers/controllers/copy_controller.js +5 -0
  10. data/app/javascript/mission_control/servers/controllers/url_maker_controller.js +22 -0
  11. data/app/models/mission_control/servers/project.rb +2 -0
  12. data/app/models/mission_control/servers/public_project.rb +6 -0
  13. data/app/models/mission_control/servers/service_setting.rb +5 -0
  14. data/app/views/mission_control/servers/dashboards/last_seens/show.html.erb +1 -1
  15. data/app/views/mission_control/servers/dashboards/project_tables/show.html.erb +14 -1
  16. data/app/views/mission_control/servers/projects/_form.html.erb +0 -4
  17. data/app/views/mission_control/servers/projects/_tabs.html.erb +20 -0
  18. data/app/views/mission_control/servers/projects/index.html.erb +18 -0
  19. data/app/views/mission_control/servers/projects/show.html.erb +6 -18
  20. data/app/views/mission_control/servers/public_projects/_form.html.erb +11 -0
  21. data/app/views/mission_control/servers/public_projects/edit.html.erb +13 -0
  22. data/app/views/mission_control/servers/public_projects/index.html.erb +54 -0
  23. data/app/views/mission_control/servers/public_projects/new.html.erb +5 -0
  24. data/app/views/mission_control/servers/public_projects/show.html.erb +22 -0
  25. data/app/views/mission_control/servers/service_settings/edit.html.erb +23 -0
  26. data/config/routes.rb +2 -0
  27. data/db/migrate/20240211190130_create_mission_control_servers_service_settings.rb +11 -0
  28. data/db/migrate/20240211235451_create_mission_control_servers_public_projects.rb +12 -0
  29. data/lib/mission_control/servers/version.rb +1 -1
  30. metadata +24 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc4409da6282e65c894099e85c2386922bf4720cf2501c7f303ae4ab53feec1b
4
- data.tar.gz: ba445e16464f045a01623072ab1e67343757d14604ad67f9e199863969984f4f
3
+ metadata.gz: 51cf1a54851d35a126051252271516032d66594697a96667fff5fddc32093048
4
+ data.tar.gz: 34e72f6c4abb6e84b711b17927cd4728eff95a16d55705f24f1a6683e9788090
5
5
  SHA512:
6
- metadata.gz: 9b7e78d6f062679dff60f16360af3aee1edfde5c696dcd3189c39e029039be81916b5856538cdc1af0a9f62f2b928df1ede6b5c06af79a55438739102740687d
7
- data.tar.gz: c6596961ef8050eaa270ca7b052c5a588ef2438a9958096ea65ac64bf03f5647a8946031b11c603daba4c21ac3d83667d1f2913ada6f12a68c079e2d1f9539df
6
+ metadata.gz: 8c3165401b25fbd1d2f3b2637cc43957c47b71a8ce704d14c0db0ae5463877ee5c2d86520704944301e3d6cb97d1f8cad25aed38f1514a08ed25e4fae36c0bda
7
+ data.tar.gz: d762e6860024afa63e5674112b468d25a7034254165bc6b06c660050996d3496afd0a3d80d7943443d357d11dffbf42dce154cadb1933f16a41b0a552d17d5b8
data/README.md CHANGED
@@ -93,14 +93,18 @@ http://localhost:3000/mission_control-servers/projects?interval=30&dark=true&com
93
93
  ## Protecting the Dashboard
94
94
 
95
95
  You can protect the dashboard by using a constraint. This will allow you to only allow certain users to access
96
- the dashboard. However, the ingress still needs to be accessible by the servers which are being monitored.
96
+ the dashboard. However, the ingress still needs to be accessible by the servers which are being monitored. In
97
+ order to install the script on the servers, you also have to expose the endpoint for the script.
97
98
 
98
99
  ```ruby
99
100
  Rails.application.routes.draw do
101
+ get '/mission_control-servers/projects/:project_id/script', to: 'mission_control/servers/scripts#show'
102
+ post '/mission_control-servers/projects/:project_id/ingress', to: 'mission_control/servers/ingresses#create'
103
+
100
104
  constraints AdminConstraint do
101
105
  mount MissionControl::Servers::Engine => "/mission_control-servers"
102
106
  end
103
- post '/mission_control-servers/projects/:project_id/ingress', to: 'mission_control/servers/ingresses#create'
107
+
104
108
  end
105
109
  ```
106
110
 
@@ -1 +1 @@
1
- /*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter var,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}body{background-color:rgb(255 255 255/var(--tw-bg-opacity));color:rgb(17 24 39/var(--tw-text-opacity))}:is(.dark body),body{--tw-bg-opacity:1;--tw-text-opacity:1}:is(.dark body){background-color:rgb(17 24 39/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}h1,h2,h3,h4,h5,h6{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark h1),:is(.dark h2),:is(.dark h3),:is(.dark h4),:is(.dark h5),:is(.dark h6){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.text-gray-light{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}:is(.dark .text-gray-light){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-normal{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}:is(.dark .text-gray-normal){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.btn{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-inset:inset;align-items:center;border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);display:inline-flex;font-size:.875rem;font-weight:600;line-height:1.25rem;padding:.5rem .75rem}.btn-default{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));color:rgb(17 24 39/var(--tw-text-opacity))}.btn-default:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}:is(.dark .btn-default){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(75 85 99/var(--tw-ring-opacity));background-color:rgb(31 41 55/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .btn-default:hover){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.btn-primary{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity));background-color:rgb(79 70 229/var(--tw-bg-opacity));color:rgb(255 255 255/var(--tw-text-opacity))}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}:is(.dark .btn-primary){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(129 140 248/var(--tw-ring-opacity));background-color:rgb(49 46 129/var(--tw-bg-opacity));color:rgb(156 163 175/var(--tw-text-opacity))}:is(.dark .btn-primary:hover){--tw-bg-opacity:1;background-color:rgb(129 140 248/var(--tw-bg-opacity))}.btn-danger{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(239 68 68/var(--tw-ring-opacity));background-color:rgb(220 38 38/var(--tw-bg-opacity));color:rgb(255 255 255/var(--tw-text-opacity))}.btn-danger:hover{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}:is(.dark .btn-danger){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity));background-color:rgb(127 29 29/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .btn-danger:hover){--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity))}.btn-primary-subtle{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity));color:rgb(79 70 229/var(--tw-text-opacity))}.btn-primary-subtle:hover{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}:is(.dark .btn-primary-subtle){--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity));color:rgb(199 210 254/var(--tw-text-opacity))}:is(.dark .btn-primary-subtle:hover){--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}.card-heading{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity));font-size:1.875rem;font-weight:600;letter-spacing:-.025em;line-height:2.25rem;order:-9999}:is(.dark .card-heading){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.tab-link{flex:1 1 0%;font-size:.875rem;font-weight:500;line-height:1.25rem;min-width:0;overflow:hidden;padding:1rem;position:relative;text-align:center}.tab-link:focus{z-index:10}.tab-link{--tw-text-opacity:1;align-items:center;color:rgb(107 114 128/var(--tw-text-opacity));display:flex;justify-content:center}:is(.dark .tab-link){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.tab-link{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.tab-link:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}:is(.dark .tab-link){--tw-bg-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity))}.progress-bar{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}:is(.dark .progress-bar){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.progress{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}:is(.dark .progress){--tw-bg-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity))}.pie-chart{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity));font-size:.875rem;font-weight:600;height:200px;line-height:1.5rem}.stat-chart{font-size:3.75rem;line-height:1}.connected-chart,.stat-chart{--tw-text-opacity:1;align-items:center;color:rgb(75 85 99/var(--tw-text-opacity));display:flex;font-weight:600;height:200px;justify-content:center;line-height:1.5rem;padding-top:1.25rem;text-align:center}.connected-chart{font-size:1.875rem}.line-chart{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity));font-size:.875rem;font-weight:600;height:400px;line-height:1.5rem;width:100%}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.top-0{top:0}.isolate{isolation:isolate}.z-10{z-index:10}.m-4{margin:1rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-10{margin-bottom:2.5rem;margin-top:2.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.h-0{height:0}.h-full{height:100%}.min-h-full{min-height:100%}.w-0{width:0}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-left-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-right-width:calc(1px*var(--tw-divide-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-l-lg{border-bottom-left-radius:.5rem;border-top-left-radius:.5rem}.rounded-r-lg{border-bottom-right-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.bg-gray-400\/5{background-color:#9ca3af0d}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-75{--tw-bg-opacity:0.75}.p-2{padding:.5rem}.p-4{padding:1rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-3\.5{padding-bottom:.875rem;padding-top:.875rem}.py-4{padding-top:1rem}.pb-4,.py-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-all{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.hover\:bg-blue-600:hover{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}:is(.dark .dark\:divide-gray-500)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(107 114 128/var(--tw-divide-opacity))}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}:is(.dark .dark\:bg-indigo-900){--tw-bg-opacity:1;background-color:rgb(49 46 129/var(--tw-bg-opacity))}@media (min-width:640px){.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:my-8{margin-bottom:2rem;margin-top:2rem}.sm\:ml-16{margin-left:4rem}.sm\:mt-0{margin-top:0}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:w-full{width:100%}.sm\:flex-auto{flex:1 1 auto}.sm\:flex-none{flex:none}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-8{padding-bottom:2rem;padding-top:2rem}.sm\:pl-6{padding-left:1.5rem}.sm\:pr-6{padding-right:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:tracking-tight{letter-spacing:-.025em}}@media (min-width:768px){.md\:max-w-3xl{max-width:48rem}}@media (min-width:1024px){.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:ml-4{margin-left:1rem}.lg\:mt-0{margin-top:0}.lg\:flex{display:flex}.lg\:table-cell{display:table-cell}.lg\:max-w-none{max-width:none}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}.lg\:px-8{padding-left:2rem}.lg\:pr-8,.lg\:px-8{padding-right:2rem}}
1
+ /*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter var,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-radius:0;border-width:1px;font-size:1rem;line-height:1.5rem;padding:.5rem .75rem}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);border-color:#2563eb;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-bottom:0;padding-top:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{--tw-shadow:0 0 #0000;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;background-origin:border-box;border-color:#6b7280;border-width:1px;color:#2563eb;display:inline-block;flex-shrink:0;height:1rem;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;width:1rem}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);outline:2px solid #0000;outline-offset:2px}[type=checkbox]:checked,[type=radio]:checked{background-color:currentColor;background-position:50%;background-repeat:no-repeat;background-size:100% 100%;border-color:#0000}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E")}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=checkbox]:indeterminate,[type=radio]:checked:focus,[type=radio]:checked:hover{background-color:currentColor;border-color:#0000}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");background-position:50%;background-repeat:no-repeat;background-size:100% 100%}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{background-color:currentColor;border-color:#0000}[type=file]{background:unset;border-color:inherit;border-radius:0;border-width:0;font-size:unset;line-height:inherit;padding:0}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}body{background-color:rgb(255 255 255/var(--tw-bg-opacity));color:rgb(17 24 39/var(--tw-text-opacity))}:is(.dark body),body{--tw-bg-opacity:1;--tw-text-opacity:1}:is(.dark body){background-color:rgb(17 24 39/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}h1,h2,h3,h4,h5,h6{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}:is(.dark h1),:is(.dark h2),:is(.dark h3),:is(.dark h4),:is(.dark h5),:is(.dark h6){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.text-gray-light{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}:is(.dark .text-gray-light){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-normal{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}:is(.dark .text-gray-normal){--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.btn{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-inset:inset;align-items:center;border-radius:.375rem;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);display:inline-flex;font-size:.875rem;font-weight:600;line-height:1.25rem;padding:.5rem .75rem}.btn-default{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity));background-color:rgb(255 255 255/var(--tw-bg-opacity));color:rgb(17 24 39/var(--tw-text-opacity))}.btn-default:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}:is(.dark .btn-default){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(75 85 99/var(--tw-ring-opacity));background-color:rgb(31 41 55/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .btn-default:hover){--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.btn-primary{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity));background-color:rgb(79 70 229/var(--tw-bg-opacity));color:rgb(255 255 255/var(--tw-text-opacity))}.btn-primary:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}:is(.dark .btn-primary){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(129 140 248/var(--tw-ring-opacity));background-color:rgb(49 46 129/var(--tw-bg-opacity));color:rgb(156 163 175/var(--tw-text-opacity))}:is(.dark .btn-primary:hover){--tw-bg-opacity:1;background-color:rgb(129 140 248/var(--tw-bg-opacity))}.btn-danger{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(239 68 68/var(--tw-ring-opacity));background-color:rgb(220 38 38/var(--tw-bg-opacity));color:rgb(255 255 255/var(--tw-text-opacity))}.btn-danger:hover{--tw-bg-opacity:1;background-color:rgb(185 28 28/var(--tw-bg-opacity))}:is(.dark .btn-danger){--tw-bg-opacity:1;--tw-text-opacity:1;--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity));background-color:rgb(127 29 29/var(--tw-bg-opacity));color:rgb(209 213 219/var(--tw-text-opacity))}:is(.dark .btn-danger:hover){--tw-bg-opacity:1;background-color:rgb(248 113 113/var(--tw-bg-opacity))}.btn-primary-subtle{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity));color:rgb(79 70 229/var(--tw-text-opacity))}.btn-primary-subtle:hover{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}:is(.dark .btn-primary-subtle){--tw-bg-opacity:1;--tw-text-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity));color:rgb(199 210 254/var(--tw-text-opacity))}:is(.dark .btn-primary-subtle:hover){--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}.card-heading{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity));font-size:1.875rem;font-weight:600;letter-spacing:-.025em;line-height:2.25rem;order:-9999}:is(.dark .card-heading){--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.tab-link{flex:1 1 0%;font-size:.875rem;font-weight:500;line-height:1.25rem;min-width:0;overflow:hidden;padding:1rem;position:relative;text-align:center}.tab-link:focus{z-index:10}.tab-link{--tw-text-opacity:1;align-items:center;color:rgb(107 114 128/var(--tw-text-opacity));display:flex;justify-content:center}:is(.dark .tab-link){--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.tab-link{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.tab-link:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}:is(.dark .tab-link){--tw-bg-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity))}.progress-bar{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}:is(.dark .progress-bar){--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.progress{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}:is(.dark .progress){--tw-bg-opacity:1;background-color:rgb(55 48 163/var(--tw-bg-opacity))}.pie-chart{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity));font-size:.875rem;font-weight:600;height:200px;line-height:1.5rem}.stat-chart{font-size:3.75rem;line-height:1}.connected-chart,.stat-chart{--tw-text-opacity:1;align-items:center;color:rgb(75 85 99/var(--tw-text-opacity));display:flex;font-weight:600;height:200px;justify-content:center;line-height:1.5rem;padding-top:1.25rem;text-align:center}.connected-chart{font-size:1.875rem}.line-chart{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity));font-size:.875rem;font-weight:600;height:400px;line-height:1.5rem;width:100%}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.top-0{top:0}.isolate{isolation:isolate}.z-10{z-index:10}.m-4{margin:1rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-bottom:-.5rem;margin-top:-.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.my-10{margin-bottom:2.5rem;margin-top:2.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.h-0{height:0}.h-full{height:100%}.h-4{height:1rem}.min-h-full{min-height:100%}.w-0{width:0}.w-full{width:100%}.w-10{width:2.5rem}.w-20{width:5rem}.w-4{width:1rem}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0}.gap-0\.5{gap:.125rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-x>:not([hidden])~:not([hidden]){--tw-divide-x-reverse:0;border-left-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-right-width:calc(1px*var(--tw-divide-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-l-lg{border-bottom-left-radius:.5rem;border-top-left-radius:.5rem}.rounded-r-lg{border-bottom-right-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-0{border-width:0}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity))}.bg-gray-400\/5{background-color:#9ca3af0d}.bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-transparent{background-color:initial}.bg-opacity-75{--tw-bg-opacity:0.75}.p-2{padding:.5rem}.p-4{padding:1rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-3\.5{padding-bottom:.875rem;padding-top:.875rem}.py-4{padding-top:1rem}.pb-4,.py-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-5{padding-top:1.25rem}.pl-1{padding-left:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-gray-300{--tw-ring-opacity:1;--tw-ring-color:rgb(209 213 219/var(--tw-ring-opacity))}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-150,.transition-all{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.placeholder\:text-gray-400::-moz-placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.placeholder\:text-gray-400::placeholder{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.hover\:bg-blue-600:hover{--tw-bg-opacity:1;background-color:rgb(37 99 235/var(--tw-bg-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-0:focus,.focus\:ring-1:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}:is(.dark .dark\:divide-gray-500)>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(107 114 128/var(--tw-divide-opacity))}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}:is(.dark .dark\:bg-indigo-900){--tw-bg-opacity:1;background-color:rgb(49 46 129/var(--tw-bg-opacity))}@media (min-width:640px){.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:my-8{margin-bottom:2rem;margin-top:2rem}.sm\:ml-16{margin-left:4rem}.sm\:mt-0{margin-top:0}.sm\:block{display:block}.sm\:inline{display:inline}.sm\:flex{display:flex}.sm\:w-full{width:100%}.sm\:flex-auto{flex:1 1 auto}.sm\:flex-none{flex:none}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:items-center{align-items:center}.sm\:truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-8{padding-bottom:2rem;padding-top:2rem}.sm\:pl-6{padding-left:1.5rem}.sm\:pr-6{padding-right:1.5rem}.sm\:text-3xl{font-size:1.875rem;line-height:2.25rem}.sm\:text-4xl{font-size:2.25rem;line-height:2.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:leading-6{line-height:1.5rem}.sm\:tracking-tight{letter-spacing:-.025em}}@media (min-width:768px){.md\:max-w-3xl{max-width:48rem}}@media (min-width:1024px){.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:ml-4{margin-left:1rem}.lg\:mt-0{margin-top:0}.lg\:flex{display:flex}.lg\:table-cell{display:table-cell}.lg\:max-w-none{max-width:none}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}.lg\:px-8{padding-left:2rem}.lg\:pr-8,.lg\:px-8{padding-right:2rem}}
@@ -2,6 +2,7 @@ module MissionControl::Servers
2
2
  class Dashboards::ProjectTablesController < ApplicationController
3
3
  def show
4
4
  @project = Project.includes(:services).find_by(token: params[:project_id])
5
+ @service_settings = @project.service_settings
5
6
  end
6
7
  end
7
8
  end
@@ -10,6 +10,8 @@ module MissionControl::Servers
10
10
 
11
11
  # GET /projects/1
12
12
  def show
13
+ hostnames = @project.services.pluck(:hostname).uniq
14
+ @service_settings = @project.service_settings.where(hostname: hostnames)
13
15
  @services = if params[:hostname]
14
16
  @project.services.where(hostname: params[:hostname]).group_by(&:hostname)
15
17
  else
@@ -66,7 +68,7 @@ module MissionControl::Servers
66
68
 
67
69
  # Use callbacks to share common setup or constraints between actions.
68
70
  def set_project
69
- @project = Project.find(params[:id])
71
+ @project = Project.includes(:services, :service_settings).find(params[:id])
70
72
  end
71
73
 
72
74
  # Only allow a list of trusted parameters through.
@@ -0,0 +1,56 @@
1
+ module MissionControl::Servers
2
+ class PublicProjectsController < ApplicationController
3
+ before_action :set_project
4
+
5
+ # GET /public_projects
6
+ def index
7
+ @public_projects = @project.public_projects.all
8
+ end
9
+
10
+ # GET /public_projects/1
11
+ def show
12
+ @project.public_projects.find_by!(token: params[:id])
13
+
14
+ hostnames = @project.services.pluck(:hostname).uniq
15
+ @service_settings = @project.service_settings.where(hostname: hostnames)
16
+ @services = if params[:hostname]
17
+ @project.services.where(hostname: params[:hostname]).group_by(&:hostname)
18
+ else
19
+ @project.services.group_by(&:hostname)
20
+ end
21
+ end
22
+
23
+ # GET /public_projects/new
24
+ def new
25
+ @public_project = @project.public_projects.new
26
+ end
27
+
28
+ # POST /public_projects
29
+ def create
30
+ @public_project = @project.public_projects.new(public_project_params)
31
+
32
+ if @public_project.save
33
+ redirect_to project_public_projects_path(@project), notice: "Public project was successfully created."
34
+ else
35
+ render :new, status: :unprocessable_entity
36
+ end
37
+ end
38
+
39
+ # DELETE /public_projects/1
40
+ def destroy
41
+ @public_project = @project.public_projects.find(params[:id])
42
+ @public_project.destroy!
43
+ redirect_to project_public_projects_path(@project), notice: "Public project was successfully destroyed.", status: :see_other
44
+ end
45
+
46
+ private
47
+ def set_project
48
+ @project = Project.find(params[:project_id])
49
+ end
50
+
51
+ # Only allow a list of trusted parameters through.
52
+ def public_project_params
53
+ params.require(:public_project).permit(:name)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module MissionControl::Servers
2
+ class ServiceSettingsController < ApplicationController
3
+ def edit
4
+ @project = Project.find(params[:project_id])
5
+ @service_setting = @project.service_settings.find_or_initialize_by(hostname: params[:id])
6
+ @service_setting.label = params[:id] if @service_setting.new_record?
7
+ @service_setting.save # To limit having to have a create action
8
+ end
9
+
10
+ def update
11
+ @project = Project.find(params[:project_id])
12
+ @service_setting = @project.service_settings.find_by(hostname)
13
+ @service_setting.update(label)
14
+ redirect_to projects_path, notice: "Updated Service Settings."
15
+ end
16
+
17
+ private
18
+
19
+ def hostname
20
+ params.require(:service_setting).permit(:hostname)
21
+ end
22
+
23
+ def label
24
+ params.require(:service_setting).permit(:label)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module MissionControl::Servers
2
+ module ServiceSettingsHelper
3
+ end
4
+ end
@@ -4,9 +4,14 @@ export default class extends Controller {
4
4
  static targets = ["source"];
5
5
 
6
6
  copy(event) {
7
+ event.preventDefault();
7
8
  const text = this.sourceTarget.textContent;
9
+ const originalText = event.target.textContent;
8
10
  navigator.clipboard.writeText(text).then(() => {
9
11
  event.target.textContent = "Copied!";
12
+ setTimeout(() => {
13
+ event.target.textContent = originalText;
14
+ }, 2000);
10
15
  }).catch(err => {
11
16
  console.error('Failed to copy text: ', err);
12
17
  });
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static targets = ["dark", "combo", "interval", "link", "output"]
5
+
6
+
7
+ connect() {
8
+ this.originalUrl = this.linkTarget.href;
9
+ console.log("URL Maker Controller connected");
10
+ this.updateLink(); // Call on connect to set initial state if needed
11
+ }
12
+
13
+ updateLink() {
14
+ const darkMode = this.darkTarget.checked ? "dark=true" : "dark=false";
15
+ const disableCombo = this.comboTarget.checked ? "combo=false" : "combo=true";
16
+ const interval = `interval=${this.intervalTarget.value}`;
17
+ const newUrl = `${this.originalUrl}?${darkMode}&${disableCombo}&${interval}`;
18
+
19
+ this.linkTarget.href = newUrl;
20
+ this.outputTarget.textContent = newUrl;
21
+ }
22
+ }
@@ -1,6 +1,8 @@
1
1
  module MissionControl::Servers
2
2
  class Project < ApplicationRecord
3
3
  has_many :services, dependent: :destroy
4
+ has_many :service_settings, dependent: :destroy
5
+ has_many :public_projects, dependent: :destroy
4
6
  has_secure_token :token
5
7
  end
6
8
  end
@@ -0,0 +1,6 @@
1
+ module MissionControl::Servers
2
+ class PublicProject < ApplicationRecord
3
+ belongs_to :project
4
+ has_secure_token :token, length: 128
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module MissionControl::Servers
2
+ class ServiceSetting < ApplicationRecord
3
+ belongs_to :project
4
+ end
5
+ end
@@ -6,7 +6,7 @@
6
6
  <dd class="card-heading">Last Seen</dd>
7
7
  <dt class="connected-chart">
8
8
  <%= @service.created_at.strftime("%Y-%-m-%-d") %><br><br>
9
- <%= @service.created_at.strftime("%I:%M:%S %p") %>
9
+ <%= @service.created_at.strftime("%-I:%M:%S %p") %>
10
10
  </dt>
11
11
  </div>
12
12
  <% end %>
@@ -12,6 +12,7 @@
12
12
  <th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-normal lg:table-cell">Memory Used %</th>
13
13
  <th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-normal lg:table-cell">Memory Free</th>
14
14
  <th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-normal lg:table-cell">Disk Free</th>
15
+ <th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-normal lg:table-cell">Last Seen</th>
15
16
  <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
16
17
  <span class="sr-only">Select</span>
17
18
  </th>
@@ -21,14 +22,26 @@
21
22
  <% @project.services.group_by(&:hostname).each do |hostname, services| %>
22
23
  <tr>
23
24
  <td class="relative py-4 pl-4 pr-3 text-sm sm:pl-6">
24
- <div class="font-medium text-gray-normal"><%= hostname%></div>
25
+ <div class="font-medium text-gray-normal">
26
+ <% service_setting = @service_settings.find { |s| s.hostname == hostname } %>
27
+ <% if service_setting %>
28
+ <%= service_setting.label %>
29
+ (<%= hostname %>)
30
+ <% else %>
31
+ <%= hostname %>
32
+ <% end %>
33
+ </div>
25
34
  </td>
26
35
  <td class="px-3 py-3.5 text-sm text-gray-light lg:table-cell"><%= services.last.cpu %>%</td>
27
36
  <td class="hidden px-3 py-3.5 text-sm text-gray-light lg:table-cell"><%= number_to_human_size(services.last.mem_used.to_f * 1.megabyte) %></td>
28
37
  <td class="hidden px-3 py-3.5 text-sm text-gray-light lg:table-cell"><%= services.last.mem_percent %>%</td>
29
38
  <td class="hidden px-3 py-3.5 text-sm text-gray-light lg:table-cell"><%= number_to_human_size(services.last.mem_free.to_f * 1.megabyte) %></td>
30
39
  <td class="hidden px-3 py-3.5 text-sm text-gray-light lg:table-cell"><%= services.last.disk_free %></td>
40
+ <td class="hidden px-3 py-3.5 text-sm text-gray-light lg:table-cell">
41
+ <%= services.last.created_at.strftime("%Y-%-m-%-d %-I:%M:%S %p") %>
42
+ </td>
31
43
  <td class="relative py-3.5 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
44
+ <%= link_to "Settings", edit_project_service_setting_path(@project, id: hostname), target: :_top, class: "btn btn-default" %>
32
45
  <%= link_to "Dashboard", project_path(@project, hostname: hostname), target: :_top, class: "btn btn-default" %>
33
46
  <%= link_to "Remove", project_path(@project, hostname: hostname), data: { "turbo-method": :delete, "turbo-confirm": "Are you sure?" }, class: "btn btn-danger" %>
34
47
  </td>
@@ -16,10 +16,6 @@
16
16
  <%= form.text_field :title, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
17
17
  </div>
18
18
 
19
- <div class="mb-6">
20
- <%= form.hidden_field :token %>
21
- </div>
22
-
23
19
  <div class="flex items-center justify-between">
24
20
  <%= form.submit class: "btn btn-primary btn-block" %>
25
21
  <%= link_to "Cancel", project, class: "text-gray-light px-4 transition duration-150 ease-in-out" %>
@@ -0,0 +1,20 @@
1
+ <div class="mt-3">
2
+ <nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-500 rounded-lg shadow" aria-label="Tabs">
3
+ <% services.each_with_index do |(hostname, services), index| %>
4
+ <% first_tab_class = index.zero? ? "rounded-l-lg" : "" %>
5
+ <% last_tab_class = index == services.length - 1 ? "rounded-r-lg" : "" %>
6
+ <%= tag.a "#",
7
+ "data-action": "click->tabs#changeTab",
8
+ "data-target": hostname,
9
+ "data-tabs-target": "link",
10
+ class: "tab-link flex items-center justify-center #{first_tab_class} #{last_tab_class}" do %>
11
+ <% service_setting = service_settings.find { |s| s.hostname == hostname } %>
12
+ <% if service_setting %>
13
+ <%= service_setting.label %>
14
+ <% else %>
15
+ <%= hostname %>
16
+ <% end %>
17
+ <% end %>
18
+ <% end %>
19
+ </nav>
20
+ </div>
@@ -28,6 +28,24 @@
28
28
  <tr>
29
29
  <td class="whitespace-nowrap text-2xl font-bold tracking-tight text-gray-normal sm:text-3xl"><%= project.title %></td>
30
30
  <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6 lg:pr-8">
31
+ <span data-controller="dialog">
32
+ <%= link_to "Public Links", project_public_projects_path(project), "data-action": "click->dialog#show", "data-turbo-frame": "public_links", class: "btn btn-default" %>
33
+ <div data-dialog-target="modal" class="hidden fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
34
+ <div data-action="click->dialog#hide" class="fixed inset-0 bg-gray-500 bg-opacity-75"></div>
35
+ <div class="flex items-end justify-center min-h-full p-4 text-center sm:items-center">
36
+ <div class="relative bg-white dark:bg-gray-900 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 md:max-w-3xl sm:w-full">
37
+ <div class="px-4 pt-5 pb-4 sm:p-6">
38
+ <h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">Public Links</h3>
39
+ <div class="mt-2">
40
+ <p class="text-sm text-gray-light">
41
+ <%= turbo_frame_tag :public_links %>
42
+ </p>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </span>
31
49
  <%= link_to "Dashboards", project, target: :_top, class: "btn btn-default" %>
32
50
  <%= link_to "Remove", project, data: { "turbo-method": :delete, "turbo-confirm": "Are you sure?" }, class: "btn btn-danger" %>
33
51
  </td>
@@ -43,24 +43,12 @@
43
43
  </div>
44
44
  <% interval = params[:interval].present? ? params[:interval].to_i * 1000 : 10_000 %>
45
45
  <%= tag.div data: { controller: "tabs", "tabs-refresh-interval-value": interval } do %>
46
- <div class="mt-3">
47
- <!-- Current: "text-gray-900", Default: "text-gray-500 hover:text-gray-700" -->
48
- <nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-500 rounded-lg shadow" aria-label="Tabs">
49
- <% @services.each_with_index do |(hostname, services), index| %>
50
- <% first_tab_class = index.zero? ? "rounded-l-lg" : "" %>
51
- <% last_tab_class = index == @services.length - 1 ? "rounded-r-lg" : "" %>
52
- <%= tag.a "#",
53
- "data-action": "click->tabs#changeTab",
54
- "data-target": hostname,
55
- "data-tabs-target": "link",
56
- class: "tab-link flex items-center justify-center #{first_tab_class} #{last_tab_class}" do %>
57
- <%= hostname %>
58
- <% end %>
59
- <% end %>
60
- </nav>
61
- </div>
62
-
46
+ <%= render partial: "mission_control/servers/projects/tabs",
47
+ locals: {
48
+ services: @services,
49
+ service_settings: @service_settings
50
+ } %>
63
51
  <% @services.each do |hostname, _services| %>
64
- <%= render partial: "mission_control/servers/projects/panels", locals: { project: @project, hostname: hostname }%>
52
+ <%= render partial: "mission_control/servers/projects/panels", locals: { project: @project, hostname: hostname } %>
65
53
  <% end %>
66
54
  <% end %>
@@ -0,0 +1,11 @@
1
+ <%= form_with model: [project, public_project] do |form| %>
2
+ <div class="mb-4">
3
+ <%= form.label :name, class: "block text-gray-700 text-sm font-bold mb-2" %>
4
+ <%= form.text_field :name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
5
+ </div>
6
+
7
+ <div class="flex items-center justify-between">
8
+ <%= form.submit class: "btn btn-primary btn-block" %>
9
+ <%= link_to "Cancel", project_public_projects_path(project), class: "text-gray-light px-4 transition duration-150 ease-in-out" %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= turbo_frame_tag :public_links do %>
2
+
3
+ <h1>Editing public project</h1>
4
+
5
+ <%= render "form", public_project: @public_project %>
6
+
7
+ <br>
8
+
9
+ <div>
10
+ <%= link_to "Show this public project", @public_project %> |
11
+ <%= link_to "Back to public projects", public_projects_path %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,54 @@
1
+ <%= turbo_frame_tag :public_links do %>
2
+ <div data-controller="url-maker">
3
+ <div class="sm:flex sm:items-center">
4
+ <div class="sm:flex-auto">
5
+ <%= check_box_tag "dark", "true", false, data: { action: "change->url-maker#updateLink", url_maker_target: "dark" }, class: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" %>
6
+ Dark Mode
7
+ </div>
8
+ <div class="sm:flex-auto">
9
+ <%= check_box_tag "combo", "false", false, data: { action: "change->url-maker#updateLink", url_maker_target: "combo" }, class: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" %>
10
+ Disable Combo Chart
11
+ </div>
12
+ <div class="sm:flex-auto">
13
+ <%= text_field_tag "interval", "10", data: { action: "input->url-maker#updateLink", url_maker_target: "interval" }, class: "w-20 border-1 bg-transparent py-1.5 pl-1 text-gray-900 placeholder:text-gray-400 focus:ring-1 sm:text-sm sm:leading-6" %>
14
+ Tab Interval
15
+ </div>
16
+ <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
17
+ <%= link_to "New Public Link", new_project_public_project_path(@project), class: "btn btn-primary" %>
18
+ </div>
19
+ </div>
20
+ <table class="min-w-full divide-y divide-gray-300">
21
+ <thead>
22
+ <tr>
23
+ <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-normal sm:pl-6">Name</th>
24
+ <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
25
+ <span class="sr-only">Select</span>
26
+ </th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <% @public_projects.each do |public_project| %>
31
+ <tr>
32
+ <td class="relative py-4 pl-4 pr-3 text-sm sm:pl-6">
33
+ <div class="font-medium text-gray-normal">
34
+ <%= public_project.name %>
35
+ </div>
36
+ </td>
37
+ <td class="relative py-3.5 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
38
+ <div class="flex items-center justify-end space-x-2"> <!-- Flex container with space between items -->
39
+ <div data-controller="copy">
40
+ <%= link_to "PUBLIC LINK", project_public_project_url(@project, id: public_project.token), "data-url-maker-target": "link", class: "hidden" %>
41
+ <div data-action="click->copy#copy" class="btn btn-default">Public Link</div>
42
+ <span data-copy-target="source" class="hidden" data-url-maker-target="output">
43
+ link
44
+ </span>
45
+ </div>
46
+ <%= link_to "Remove", project_public_project_path(@project, public_project), data: { "turbo-method": :delete, "turbo-confirm": "Are you sure?" }, class: "btn btn-danger" %>
47
+ </div>
48
+ </td>
49
+ </tr>
50
+ <% end %>
51
+ </tbody>
52
+ </table>
53
+ </div>
54
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%= turbo_frame_tag :public_links do %>
2
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 my-10">
3
+ <%= render "form", project: @project, public_project: @public_project %>
4
+ </div>
5
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <div class="lg:flex lg:items-center lg:justify-between">
2
+ <div class="min-w-0 flex-1">
3
+ <h2 class="text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight"><%= @project.title %></h2>
4
+ </div>
5
+
6
+ <div class="mt-5 flex lg:ml-4 lg:mt-0">
7
+ <span class="hidden sm:block">
8
+ <button data-controller="dark-mode" data-action="click->dark-mode#toggle" class="btn btn-default">Dark Mode</button>
9
+ </span>
10
+ </div>
11
+ </div>
12
+ <% interval = params[:interval].present? ? params[:interval].to_i * 1000 : 10_000 %>
13
+ <%= tag.div data: { controller: "tabs", "tabs-refresh-interval-value": interval } do %>
14
+ <%= render partial: "mission_control/servers/projects/tabs",
15
+ locals: {
16
+ services: @services,
17
+ service_settings: @service_settings
18
+ } %>
19
+ <% @services.each do |hostname, _services| %>
20
+ <%= render partial: "mission_control/servers/projects/panels", locals: { project: @project, hostname: hostname } %>
21
+ <% end %>
22
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <div class="lg:flex lg:items-center lg:justify-between">
2
+ <div class="min-w-0 flex-1"></div>
3
+ <div class="mt-5 flex lg:ml-4 lg:mt-0">
4
+ <span class="hidden sm:block">
5
+ <button data-controller="dark-mode" data-action="click->dark-mode#toggle" class="btn btn-default">Dark Mode</button>
6
+ </span>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 my-10">
11
+ <h1 class="text-2xl text-center font-semibold text-gray-normal mb-8">Editing <%= @service_setting.hostname %></h1>
12
+ <%= form_with(model: [@project, @service_setting], class: "max-w-lg mx-auto my-10") do |form| %>
13
+ <%= form.hidden_field :hostname %>
14
+ <div class="mb-4">
15
+ <%= form.label :label, class: "block text-gray-700 text-sm font-bold mb-2" %>
16
+ <%= form.text_field :label, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
17
+ </div>
18
+ <div class="flex items-center justify-between">
19
+ <%= form.submit class: "btn btn-primary btn-block" %>
20
+ <%= link_to "Cancel", projects_path, class: "text-gray-light px-4 transition duration-150 ease-in-out" %>
21
+ </div>
22
+ <% end %>
23
+ </div>
data/config/routes.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  MissionControl::Servers::Engine.routes.draw do
2
2
  resources :projects do
3
+ resources :public_projects, except: [:edit, :update]
4
+ resources :service_settings, only: [:edit, :update]
3
5
  resource :ingress, only: :create
4
6
  resource :script, only: :show
5
7
  namespace :dashboards do
@@ -0,0 +1,11 @@
1
+ class CreateMissionControlServersServiceSettings < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :mission_control_servers_service_settings do |t|
4
+ t.belongs_to :project, null: false, foreign_key: { to_table: :mission_control_servers_projects }
5
+ t.string :hostname
6
+ t.string :label
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class CreateMissionControlServersPublicProjects < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :mission_control_servers_public_projects do |t|
4
+ t.belongs_to :project, null: false, foreign_key: { to_table: :mission_control_servers_projects }
5
+ t.string :name
6
+ t.string :token
7
+
8
+ t.timestamps
9
+ end
10
+ add_index :mission_control_servers_public_projects, :token, unique: true
11
+ end
12
+ end
@@ -1,5 +1,5 @@
1
1
  module MissionControl
2
2
  module Servers
3
- VERSION = "0.2.6"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mission_control-servers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Kimura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-11 00:00:00.000000000 Z
11
+ date: 2024-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.3
19
+ version: 7.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 7.1.3
26
+ version: 7.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: importmap-rails
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -118,8 +118,11 @@ files:
118
118
  - app/controllers/mission_control/servers/dashboards/project_tables_controller.rb
119
119
  - app/controllers/mission_control/servers/ingresses_controller.rb
120
120
  - app/controllers/mission_control/servers/projects_controller.rb
121
+ - app/controllers/mission_control/servers/public_projects_controller.rb
121
122
  - app/controllers/mission_control/servers/scripts_controller.rb
123
+ - app/controllers/mission_control/servers/service_settings_controller.rb
122
124
  - app/helpers/mission_control/servers/application_helper.rb
125
+ - app/helpers/mission_control/servers/service_settings_helper.rb
123
126
  - app/javascript/mission_control/servers/application.js
124
127
  - app/javascript/mission_control/servers/controllers/application.js
125
128
  - app/javascript/mission_control/servers/controllers/combo_chart_controller.js
@@ -131,11 +134,14 @@ files:
131
134
  - app/javascript/mission_control/servers/controllers/pie_chart_controller.js
132
135
  - app/javascript/mission_control/servers/controllers/refresh_controller.js
133
136
  - app/javascript/mission_control/servers/controllers/tabs_controller.js
137
+ - app/javascript/mission_control/servers/controllers/url_maker_controller.js
134
138
  - app/jobs/mission_control/servers/application_job.rb
135
139
  - app/mailers/mission_control/servers/application_mailer.rb
136
140
  - app/models/mission_control/servers/application_record.rb
137
141
  - app/models/mission_control/servers/project.rb
142
+ - app/models/mission_control/servers/public_project.rb
138
143
  - app/models/mission_control/servers/service.rb
144
+ - app/models/mission_control/servers/service_setting.rb
139
145
  - app/views/layouts/mission_control/servers/application.html.erb
140
146
  - app/views/mission_control/servers/dashboards/combo_histories/show.html.erb
141
147
  - app/views/mission_control/servers/dashboards/cpu_histories/show.html.erb
@@ -147,15 +153,24 @@ files:
147
153
  - app/views/mission_control/servers/dashboards/project_tables/show.html.erb
148
154
  - app/views/mission_control/servers/projects/_form.html.erb
149
155
  - app/views/mission_control/servers/projects/_panels.html.erb
156
+ - app/views/mission_control/servers/projects/_tabs.html.erb
150
157
  - app/views/mission_control/servers/projects/edit.html.erb
151
158
  - app/views/mission_control/servers/projects/index.html.erb
152
159
  - app/views/mission_control/servers/projects/new.html.erb
153
160
  - app/views/mission_control/servers/projects/show.html.erb
161
+ - app/views/mission_control/servers/public_projects/_form.html.erb
162
+ - app/views/mission_control/servers/public_projects/edit.html.erb
163
+ - app/views/mission_control/servers/public_projects/index.html.erb
164
+ - app/views/mission_control/servers/public_projects/new.html.erb
165
+ - app/views/mission_control/servers/public_projects/show.html.erb
166
+ - app/views/mission_control/servers/service_settings/edit.html.erb
154
167
  - config/importmap.rb
155
168
  - config/routes.rb
156
169
  - config/tailwind.config.js
157
170
  - db/migrate/20240205020304_create_mission_control_servers_projects.rb
158
171
  - db/migrate/20240205031009_create_mission_control_servers_services.rb
172
+ - db/migrate/20240211190130_create_mission_control_servers_service_settings.rb
173
+ - db/migrate/20240211235451_create_mission_control_servers_public_projects.rb
159
174
  - lib/mission_control/servers.rb
160
175
  - lib/mission_control/servers/configuration.rb
161
176
  - lib/mission_control/servers/engine.rb
@@ -168,7 +183,11 @@ metadata:
168
183
  homepage_uri: https://github.com/kobaltz/misson_control-servers
169
184
  source_code_uri: https://github.com/kobaltz/mission_control-servers
170
185
  changelog_uri: https://github.com/kobaltz/mission_control-servers
171
- post_install_message:
186
+ post_install_message: |
187
+ MissionControl::Servers requires additional setup. Please run the following
188
+ command to install the necessary files:
189
+
190
+ bin/rails mission_control_servers:install:migrations
172
191
  rdoc_options: []
173
192
  require_paths:
174
193
  - lib