deployed 0.1.2 → 0.1.3
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/README.md +29 -12
- data/app/assets/javascripts/deployed/application.js +74 -37
- data/app/assets/stylesheets/deployed/deployed.css +10 -132
- data/app/controllers/deployed/application_controller.rb +45 -0
- data/app/controllers/deployed/log_output_controller.rb +67 -0
- data/app/controllers/deployed/run_controller.rb +11 -142
- data/app/views/layouts/deployed/application.html.erb +11 -6
- data/config/routes.rb +3 -2
- data/lib/deployed/version.rb +1 -1
- data/lib/tasks/deployed_tasks.rake +36 -0
- metadata +5 -5
- data/app/views/layouts/deployed/_nav_menu.html.erb +0 -54
- data/lib/tasks/kamal_rails_tasks.rake +0 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 19518ebac39f541c9756046ccb53d48a531748beef2e080e06de2f151474d2a8
         | 
| 4 | 
            +
              data.tar.gz: 123c0017310d3679ccff32ea35c7c951ad71a1286045308b5081202284f68d78
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: f1d99d5a584bc58c659c195777a767296d831ded16af54180e6a1910c83e3f77fa260231d68a94392da7b7449fff92320f433f13a74a7e04282df23718d19bae
         | 
| 7 | 
            +
              data.tar.gz: 1d39f6af522fc6355e5ed6b290997556c737e58d474f8b093f0c4b85d24ac1bbd61073ccf46bb934bef54fc96f22d7bbed42911cf6b5312fb79f1e0ab1b42f5f
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,29 +1,46 @@ | |
| 1 1 | 
             
            # Deployed
         | 
| 2 2 |  | 
| 3 | 
            +
            [](https://badge.fury.io/rb/deployed)
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            Deployed is a web interface for the deployment library, [Kamal](https://kamal-deploy.org).
         | 
| 4 6 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            +
            Here is a quick video demo: https://x.com/geetfun/status/1716109581619744781?s=20
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## Requirements
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Ruby on Rails
         | 
| 7 12 |  | 
| 8 13 | 
             
            ## Installation
         | 
| 9 14 | 
             
            Add this line to your application's Gemfile:
         | 
| 10 15 |  | 
| 11 16 | 
             
            ```ruby
         | 
| 12 | 
            -
             | 
| 17 | 
            +
            group :development do
         | 
| 18 | 
            +
              gem 'kamal'
         | 
| 19 | 
            +
              gem 'deployed'
         | 
| 20 | 
            +
            end
         | 
| 13 21 | 
             
            ```
         | 
| 14 22 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
            ```bash
         | 
| 17 | 
            -
            $ bundle
         | 
| 18 | 
            -
            ```
         | 
| 23 | 
            +
            ## Usage
         | 
| 19 24 |  | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 25 | 
            +
            Add the following to your app's routes file:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ```ruby
         | 
| 28 | 
            +
            Rails.application.routes.draw do
         | 
| 29 | 
            +
              if Rails.env.development? && defined?(Deployed)
         | 
| 30 | 
            +
                mount(Deployed::Engine => '/deployed')
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              # Your other routes...
         | 
| 34 | 
            +
            end
         | 
| 23 35 | 
             
            ```
         | 
| 24 36 |  | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 37 | 
            +
            Next, head to `http://localhost:3000/deployed`
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ## Development
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            Run `bin/setup` to bootstrap the development environment.
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            To run tests: `bundle exec rake app:test`. Currently there are no tests, but some will be added soon.
         | 
| 27 44 |  | 
| 28 45 | 
             
            ## License
         | 
| 29 46 | 
             
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
| @@ -4,31 +4,20 @@ import Alpine from 'https://cdn.skypack.dev/alpinejs' | |
| 4 4 | 
             
            window.Alpine = Alpine
         | 
| 5 5 | 
             
            Alpine.start()
         | 
| 6 6 |  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
              let outputContainerEl = document.getElementById('deploy-output')
         | 
| 12 | 
            -
              let spinnerEl = document.getElementById('spinner')
         | 
| 13 | 
            -
             | 
| 14 | 
            -
              if (outputContainerEl.innerHTML !== '') {
         | 
| 15 | 
            -
                outputContainerEl.innerHTML += "<div class='py-2'></div>"
         | 
| 16 | 
            -
              }
         | 
| 7 | 
            +
            const endMarker = '[Deployed Rails] End'
         | 
| 8 | 
            +
            const outputContainerEl = document.getElementById('deploy-output')
         | 
| 9 | 
            +
            const spinnerEl = document.getElementById('spinner')
         | 
| 17 10 |  | 
| 11 | 
            +
            window.pipeLogs = () => {
         | 
| 18 12 | 
             
              spinnerEl.classList.remove('hidden')
         | 
| 19 | 
            -
               | 
| 13 | 
            +
              window.logEventSource = new EventSource(`/deployed/log_output`)
         | 
| 20 14 |  | 
| 21 | 
            -
               | 
| 15 | 
            +
              window.logEventSource.onmessage = (event) => {
         | 
| 22 16 | 
             
                if (!Alpine.store('process').running) {
         | 
| 23 | 
            -
                   | 
| 17 | 
            +
                  window.logEventSource.close()
         | 
| 24 18 | 
             
                } else {
         | 
| 25 | 
            -
                  if (event.data.includes( | 
| 26 | 
            -
                     | 
| 27 | 
            -
                    outputContainerEl.innerHTML += `<div class="text-slate-400 pb-4">Executed: <span class='text-slate-400 font-semibold'>kamal ${commandToRun}</span></div>`
         | 
| 28 | 
            -
                    spinnerEl.classList.add('hidden')
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                    // Let the frontend know we're done
         | 
| 31 | 
            -
                    Alpine.store('process').stop()
         | 
| 19 | 
            +
                  if (event.data.includes("[Deployed] End")) {
         | 
| 20 | 
            +
                    window.stopPipeLogs()
         | 
| 32 21 | 
             
                  } else {
         | 
| 33 22 | 
             
                    outputContainerEl.innerHTML += event.data
         | 
| 34 23 | 
             
                  }
         | 
| @@ -39,6 +28,46 @@ window.execDeployed = (commandToRun) => { | |
| 39 28 | 
             
              }
         | 
| 40 29 | 
             
            }
         | 
| 41 30 |  | 
| 31 | 
            +
            window.stopPipeLogs = () => {
         | 
| 32 | 
            +
              if (typeof(window.logEventSource) !== 'undefined') {
         | 
| 33 | 
            +
                window.logEventSource.close()
         | 
| 34 | 
            +
              }
         | 
| 35 | 
            +
              spinnerEl.classList.add('hidden')
         | 
| 36 | 
            +
              Alpine.store('process').stop()
         | 
| 37 | 
            +
            }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            window.execDeployed = (commandToRun) => {
         | 
| 40 | 
            +
              Alpine.store('process').start()
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              let endpoint = `/deployed/execute`
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              // Create a data object with your payload (in this case, a command)
         | 
| 45 | 
            +
              const data = { command: commandToRun }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              // Define the fetch options for the POST request
         | 
| 48 | 
            +
              const options = {
         | 
| 49 | 
            +
                method: 'POST',
         | 
| 50 | 
            +
                headers: { 'Content-Type': 'application/json' },
         | 
| 51 | 
            +
                body: JSON.stringify(data)
         | 
| 52 | 
            +
              }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              // Perform the POST request using the fetch API
         | 
| 55 | 
            +
              fetch(endpoint, options)
         | 
| 56 | 
            +
                .then(response => {
         | 
| 57 | 
            +
                  if (response.ok) {
         | 
| 58 | 
            +
                    outputContainerEl.innerHTML += "<div class='py-2'></div>"
         | 
| 59 | 
            +
                    outputContainerEl.innerHTML += `<div class='text-slate-400'>[Deployed] Command Received: kamal ${commandToRun}</div>`
         | 
| 60 | 
            +
                    window.pipeLogs()
         | 
| 61 | 
            +
                    return response.json(); // Parse the JSON response if needed
         | 
| 62 | 
            +
                  } else {
         | 
| 63 | 
            +
                    throw new Error('Network response was not ok');
         | 
| 64 | 
            +
                  }
         | 
| 65 | 
            +
                })
         | 
| 66 | 
            +
                .catch(error => {
         | 
| 67 | 
            +
                  console.error('Fetch error:', error)
         | 
| 68 | 
            +
                })
         | 
| 69 | 
            +
            }
         | 
| 70 | 
            +
             | 
| 42 71 | 
             
            window.abortDeployed = () => {
         | 
| 43 72 | 
             
              // Let the frontend know we're starting
         | 
| 44 73 | 
             
              Alpine.store('process').startAbort()
         | 
| @@ -46,26 +75,34 @@ window.abortDeployed = () => { | |
| 46 75 | 
             
              let outputContainerEl = document.getElementById('deploy-output')
         | 
| 47 76 | 
             
              let spinnerEl = document.getElementById('spinner')
         | 
| 48 77 |  | 
| 49 | 
            -
              outputContainerEl.innerHTML += `<div class="text-red-400 | 
| 50 | 
            -
             | 
| 51 | 
            -
              var source = new EventSource(`/deployed/cancel`)
         | 
| 78 | 
            +
              outputContainerEl.innerHTML += `<div class="text-red-400">Aborting...</div>`
         | 
| 52 79 |  | 
| 53 | 
            -
               | 
| 54 | 
            -
                if (event.data.includes('[Deployed Rails] End transmission')) {
         | 
| 55 | 
            -
                  source.close()
         | 
| 80 | 
            +
              let endpoint = `/deployed/cancel`
         | 
| 56 81 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
                  Alpine.store('process').stop()
         | 
| 61 | 
            -
                  Alpine.store('process').resetAbort()
         | 
| 62 | 
            -
                } else {
         | 
| 63 | 
            -
                  outputContainerEl.innerHTML += event.data
         | 
| 64 | 
            -
                }
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                outputContainerEl.scrollIntoView({ behavior: "smooth", block: "end" })
         | 
| 67 | 
            -
                spinnerEl.scrollIntoView({ behavior: "smooth", block: "end" })
         | 
| 82 | 
            +
              const options = {
         | 
| 83 | 
            +
                method: 'POST',
         | 
| 84 | 
            +
                headers: { 'Content-Type': 'application/json' }
         | 
| 68 85 | 
             
              }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              // Perform the POST request using the fetch API
         | 
| 88 | 
            +
              fetch(endpoint, options)
         | 
| 89 | 
            +
                .then(response => {
         | 
| 90 | 
            +
                  if (response.ok) {
         | 
| 91 | 
            +
                    window.stopPipeLogs()
         | 
| 92 | 
            +
                    Alpine.store('process').stop()
         | 
| 93 | 
            +
                    Alpine.store('process').resetAbort()
         | 
| 94 | 
            +
                    return response.json(); // Parse the JSON response if needed
         | 
| 95 | 
            +
                  } else {
         | 
| 96 | 
            +
                    throw new Error('Network response was not ok');
         | 
| 97 | 
            +
                  }
         | 
| 98 | 
            +
                })
         | 
| 99 | 
            +
                .then(data => {
         | 
| 100 | 
            +
                  console.log(data)
         | 
| 101 | 
            +
                  outputContainerEl.innerHTML += `<div class="text-yellow-400">Aborted process with PID ${data.message}</div>`
         | 
| 102 | 
            +
                })
         | 
| 103 | 
            +
                .catch(error => {
         | 
| 104 | 
            +
                  console.error('Fetch error:', error)
         | 
| 105 | 
            +
                })
         | 
| 69 106 | 
             
            }
         | 
| 70 107 |  | 
| 71 108 | 
             
            // Some other JS that probably should be refactored at some point...
         | 
| @@ -725,10 +725,6 @@ select { | |
| 725 725 | 
             
              left: 0px;
         | 
| 726 726 | 
             
            }
         | 
| 727 727 |  | 
| 728 | 
            -
            .left-\[-225px\] {
         | 
| 729 | 
            -
              left: -225px;
         | 
| 730 | 
            -
            }
         | 
| 731 | 
            -
             | 
| 732 728 | 
             
            .right-0 {
         | 
| 733 729 | 
             
              right: 0px;
         | 
| 734 730 | 
             
            }
         | 
| @@ -779,10 +775,6 @@ select { | |
| 779 775 | 
             
              margin-bottom: 1.25rem;
         | 
| 780 776 | 
             
            }
         | 
| 781 777 |  | 
| 782 | 
            -
            .ml-4 {
         | 
| 783 | 
            -
              margin-left: 1rem;
         | 
| 784 | 
            -
            }
         | 
| 785 | 
            -
             | 
| 786 778 | 
             
            .mr-2 {
         | 
| 787 779 | 
             
              margin-right: 0.5rem;
         | 
| 788 780 | 
             
            }
         | 
| @@ -823,10 +815,6 @@ select { | |
| 823 815 | 
             
              display: none;
         | 
| 824 816 | 
             
            }
         | 
| 825 817 |  | 
| 826 | 
            -
            .h-11 {
         | 
| 827 | 
            -
              height: 2.75rem;
         | 
| 828 | 
            -
            }
         | 
| 829 | 
            -
             | 
| 830 818 | 
             
            .h-5 {
         | 
| 831 819 | 
             
              height: 1.25rem;
         | 
| 832 820 | 
             
            }
         | 
| @@ -851,10 +839,6 @@ select { | |
| 851 839 | 
             
              min-height: 100%;
         | 
| 852 840 | 
             
            }
         | 
| 853 841 |  | 
| 854 | 
            -
            .w-11 {
         | 
| 855 | 
            -
              width: 2.75rem;
         | 
| 856 | 
            -
            }
         | 
| 857 | 
            -
             | 
| 858 842 | 
             
            .w-5 {
         | 
| 859 843 | 
             
              width: 1.25rem;
         | 
| 860 844 | 
             
            }
         | 
| @@ -879,37 +863,15 @@ select { | |
| 879 863 | 
             
              max-width: 32rem;
         | 
| 880 864 | 
             
            }
         | 
| 881 865 |  | 
| 882 | 
            -
            .max-w-max {
         | 
| 883 | 
            -
              max-width: -moz-max-content;
         | 
| 884 | 
            -
              max-width: max-content;
         | 
| 885 | 
            -
            }
         | 
| 886 | 
            -
             | 
| 887 | 
            -
            .max-w-md {
         | 
| 888 | 
            -
              max-width: 28rem;
         | 
| 889 | 
            -
            }
         | 
| 890 | 
            -
             | 
| 891 866 | 
             
            .flex-1 {
         | 
| 892 867 | 
             
              flex: 1 1 0%;
         | 
| 893 868 | 
             
            }
         | 
| 894 869 |  | 
| 895 | 
            -
            .flex-auto {
         | 
| 896 | 
            -
              flex: 1 1 auto;
         | 
| 897 | 
            -
            }
         | 
| 898 | 
            -
             | 
| 899 | 
            -
            .flex-none {
         | 
| 900 | 
            -
              flex: none;
         | 
| 901 | 
            -
            }
         | 
| 902 | 
            -
             | 
| 903 870 | 
             
            .translate-y-0 {
         | 
| 904 871 | 
             
              --tw-translate-y: 0px;
         | 
| 905 872 | 
             
              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));
         | 
| 906 873 | 
             
            }
         | 
| 907 874 |  | 
| 908 | 
            -
            .translate-y-1 {
         | 
| 909 | 
            -
              --tw-translate-y: 0.25rem;
         | 
| 910 | 
            -
              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));
         | 
| 911 | 
            -
            }
         | 
| 912 | 
            -
             | 
| 913 875 | 
             
            .translate-y-4 {
         | 
| 914 876 | 
             
              --tw-translate-y: 1rem;
         | 
| 915 877 | 
             
              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));
         | 
| @@ -963,25 +925,11 @@ select { | |
| 963 925 | 
             
              justify-content: center;
         | 
| 964 926 | 
             
            }
         | 
| 965 927 |  | 
| 966 | 
            -
            .gap-x-1 {
         | 
| 967 | 
            -
              -moz-column-gap: 0.25rem;
         | 
| 968 | 
            -
                   column-gap: 0.25rem;
         | 
| 969 | 
            -
            }
         | 
| 970 | 
            -
             | 
| 971 | 
            -
            .gap-x-6 {
         | 
| 972 | 
            -
              -moz-column-gap: 1.5rem;
         | 
| 973 | 
            -
                   column-gap: 1.5rem;
         | 
| 974 | 
            -
            }
         | 
| 975 | 
            -
             | 
| 976 928 | 
             
            .gap-x-8 {
         | 
| 977 929 | 
             
              -moz-column-gap: 2rem;
         | 
| 978 930 | 
             
                   column-gap: 2rem;
         | 
| 979 931 | 
             
            }
         | 
| 980 932 |  | 
| 981 | 
            -
            .gap-y-1 {
         | 
| 982 | 
            -
              row-gap: 0.25rem;
         | 
| 983 | 
            -
            }
         | 
| 984 | 
            -
             | 
| 985 933 | 
             
            .gap-y-4 {
         | 
| 986 934 | 
             
              row-gap: 1rem;
         | 
| 987 935 | 
             
            }
         | 
| @@ -992,6 +940,12 @@ select { | |
| 992 940 | 
             
              margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
         | 
| 993 941 | 
             
            }
         | 
| 994 942 |  | 
| 943 | 
            +
            .space-x-2 > :not([hidden]) ~ :not([hidden]) {
         | 
| 944 | 
            +
              --tw-space-x-reverse: 0;
         | 
| 945 | 
            +
              margin-right: calc(0.5rem * var(--tw-space-x-reverse));
         | 
| 946 | 
            +
              margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
         | 
| 947 | 
            +
            }
         | 
| 948 | 
            +
             | 
| 995 949 | 
             
            .space-y-10 > :not([hidden]) ~ :not([hidden]) {
         | 
| 996 950 | 
             
              --tw-space-y-reverse: 0;
         | 
| 997 951 | 
             
              margin-top: calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));
         | 
| @@ -1004,18 +958,6 @@ select { | |
| 1004 958 | 
             
              margin-bottom: calc(1rem * var(--tw-space-y-reverse));
         | 
| 1005 959 | 
             
            }
         | 
| 1006 960 |  | 
| 1007 | 
            -
            .space-x-3 > :not([hidden]) ~ :not([hidden]) {
         | 
| 1008 | 
            -
              --tw-space-x-reverse: 0;
         | 
| 1009 | 
            -
              margin-right: calc(0.75rem * var(--tw-space-x-reverse));
         | 
| 1010 | 
            -
              margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
         | 
| 1011 | 
            -
            }
         | 
| 1012 | 
            -
             | 
| 1013 | 
            -
            .space-x-2 > :not([hidden]) ~ :not([hidden]) {
         | 
| 1014 | 
            -
              --tw-space-x-reverse: 0;
         | 
| 1015 | 
            -
              margin-right: calc(0.5rem * var(--tw-space-x-reverse));
         | 
| 1016 | 
            -
              margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
         | 
| 1017 | 
            -
            }
         | 
| 1018 | 
            -
             | 
| 1019 961 | 
             
            .divide-y > :not([hidden]) ~ :not([hidden]) {
         | 
| 1020 962 | 
             
              --tw-divide-y-reverse: 0;
         | 
| 1021 963 | 
             
              border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
         | 
| @@ -1043,10 +985,6 @@ select { | |
| 1043 985 | 
             
              overflow-y: auto;
         | 
| 1044 986 | 
             
            }
         | 
| 1045 987 |  | 
| 1046 | 
            -
            .rounded-3xl {
         | 
| 1047 | 
            -
              border-radius: 1.5rem;
         | 
| 1048 | 
            -
            }
         | 
| 1049 | 
            -
             | 
| 1050 988 | 
             
            .rounded-full {
         | 
| 1051 989 | 
             
              border-radius: 9999px;
         | 
| 1052 990 | 
             
            }
         | 
| @@ -1067,18 +1005,14 @@ select { | |
| 1067 1005 | 
             
              border-width: 1px;
         | 
| 1068 1006 | 
             
            }
         | 
| 1069 1007 |  | 
| 1070 | 
            -
            .border-t {
         | 
| 1071 | 
            -
              border-top-width: 1px;
         | 
| 1072 | 
            -
            }
         | 
| 1073 | 
            -
             | 
| 1074 | 
            -
            .border-b-2 {
         | 
| 1075 | 
            -
              border-bottom-width: 2px;
         | 
| 1076 | 
            -
            }
         | 
| 1077 | 
            -
             | 
| 1078 1008 | 
             
            .border-b-4 {
         | 
| 1079 1009 | 
             
              border-bottom-width: 4px;
         | 
| 1080 1010 | 
             
            }
         | 
| 1081 1011 |  | 
| 1012 | 
            +
            .border-t {
         | 
| 1013 | 
            +
              border-top-width: 1px;
         | 
| 1014 | 
            +
            }
         | 
| 1015 | 
            +
             | 
| 1082 1016 | 
             
            .border-slate-200 {
         | 
| 1083 1017 | 
             
              --tw-border-opacity: 1;
         | 
| 1084 1018 | 
             
              border-color: rgb(226 232 240 / var(--tw-border-opacity));
         | 
| @@ -1089,11 +1023,6 @@ select { | |
| 1089 1023 | 
             
              border-color: rgb(203 213 225 / var(--tw-border-opacity));
         | 
| 1090 1024 | 
             
            }
         | 
| 1091 1025 |  | 
| 1092 | 
            -
            .bg-gray-50 {
         | 
| 1093 | 
            -
              --tw-bg-opacity: 1;
         | 
| 1094 | 
            -
              background-color: rgb(249 250 251 / var(--tw-bg-opacity));
         | 
| 1095 | 
            -
            }
         | 
| 1096 | 
            -
             | 
| 1097 1026 | 
             
            .bg-gray-500 {
         | 
| 1098 1027 | 
             
              --tw-bg-opacity: 1;
         | 
| 1099 1028 | 
             
              background-color: rgb(107 114 128 / var(--tw-bg-opacity));
         | 
| @@ -1261,14 +1190,6 @@ select { | |
| 1261 1190 | 
             
              padding-top: 2.5rem;
         | 
| 1262 1191 | 
             
            }
         | 
| 1263 1192 |  | 
| 1264 | 
            -
            .pt-\[52px\] {
         | 
| 1265 | 
            -
              padding-top: 52px;
         | 
| 1266 | 
            -
            }
         | 
| 1267 | 
            -
             | 
| 1268 | 
            -
            .pt-\[55px\] {
         | 
| 1269 | 
            -
              padding-top: 55px;
         | 
| 1270 | 
            -
            }
         | 
| 1271 | 
            -
             | 
| 1272 1193 | 
             
            .pt-\[56px\] {
         | 
| 1273 1194 | 
             
              padding-top: 56px;
         | 
| 1274 1195 | 
             
            }
         | 
| @@ -1334,11 +1255,6 @@ select { | |
| 1334 1255 | 
             
              color: rgb(107 114 128 / var(--tw-text-opacity));
         | 
| 1335 1256 | 
             
            }
         | 
| 1336 1257 |  | 
| 1337 | 
            -
            .text-gray-600 {
         | 
| 1338 | 
            -
              --tw-text-opacity: 1;
         | 
| 1339 | 
            -
              color: rgb(75 85 99 / var(--tw-text-opacity));
         | 
| 1340 | 
            -
            }
         | 
| 1341 | 
            -
             | 
| 1342 1258 | 
             
            .text-gray-700 {
         | 
| 1343 1259 | 
             
              --tw-text-opacity: 1;
         | 
| 1344 1260 | 
             
              color: rgb(55 65 81 / var(--tw-text-opacity));
         | 
| @@ -1424,12 +1340,6 @@ select { | |
| 1424 1340 | 
             
              opacity: 0.75;
         | 
| 1425 1341 | 
             
            }
         | 
| 1426 1342 |  | 
| 1427 | 
            -
            .shadow-lg {
         | 
| 1428 | 
            -
              --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
         | 
| 1429 | 
            -
              --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
         | 
| 1430 | 
            -
              box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
         | 
| 1431 | 
            -
            }
         | 
| 1432 | 
            -
             | 
| 1433 1343 | 
             
            .shadow-md {
         | 
| 1434 1344 | 
             
              --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
         | 
| 1435 1345 | 
             
              --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
         | 
| @@ -1467,14 +1377,6 @@ select { | |
| 1467 1377 | 
             
              --tw-ring-color: rgb(17 24 39 / 0.05);
         | 
| 1468 1378 | 
             
            }
         | 
| 1469 1379 |  | 
| 1470 | 
            -
            .transition {
         | 
| 1471 | 
            -
              transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
         | 
| 1472 | 
            -
              transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
         | 
| 1473 | 
            -
              transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
         | 
| 1474 | 
            -
              transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
         | 
| 1475 | 
            -
              transition-duration: 150ms;
         | 
| 1476 | 
            -
            }
         | 
| 1477 | 
            -
             | 
| 1478 1380 | 
             
            .transition-all {
         | 
| 1479 1381 | 
             
              transition-property: all;
         | 
| 1480 1382 | 
             
              transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
         | 
| @@ -1487,10 +1389,6 @@ select { | |
| 1487 1389 | 
             
              transition-duration: 150ms;
         | 
| 1488 1390 | 
             
            }
         | 
| 1489 1391 |  | 
| 1490 | 
            -
            .duration-150 {
         | 
| 1491 | 
            -
              transition-duration: 150ms;
         | 
| 1492 | 
            -
            }
         | 
| 1493 | 
            -
             | 
| 1494 1392 | 
             
            .duration-200 {
         | 
| 1495 1393 | 
             
              transition-duration: 200ms;
         | 
| 1496 1394 | 
             
            }
         | 
| @@ -1569,16 +1467,6 @@ select { | |
| 1569 1467 | 
             
              outline-color: #0284c7;
         | 
| 1570 1468 | 
             
            }
         | 
| 1571 1469 |  | 
| 1572 | 
            -
            .group:hover .group-hover\:bg-white {
         | 
| 1573 | 
            -
              --tw-bg-opacity: 1;
         | 
| 1574 | 
            -
              background-color: rgb(255 255 255 / var(--tw-bg-opacity));
         | 
| 1575 | 
            -
            }
         | 
| 1576 | 
            -
             | 
| 1577 | 
            -
            .group:hover .group-hover\:text-indigo-600 {
         | 
| 1578 | 
            -
              --tw-text-opacity: 1;
         | 
| 1579 | 
            -
              color: rgb(79 70 229 / var(--tw-text-opacity));
         | 
| 1580 | 
            -
            }
         | 
| 1581 | 
            -
             | 
| 1582 1470 | 
             
            @media (min-width: 640px) {
         | 
| 1583 1471 | 
             
              .sm\:col-span-2 {
         | 
| 1584 1472 | 
             
                grid-column: span 2 / span 2;
         | 
| @@ -1623,13 +1511,3 @@ select { | |
| 1623 1511 | 
             
                grid-template-columns: repeat(3, minmax(0, 1fr));
         | 
| 1624 1512 | 
             
              }
         | 
| 1625 1513 | 
             
            }
         | 
| 1626 | 
            -
             | 
| 1627 | 
            -
            @media (min-width: 1024px) {
         | 
| 1628 | 
            -
              .lg\:max-w-2xl {
         | 
| 1629 | 
            -
                max-width: 42rem;
         | 
| 1630 | 
            -
              }
         | 
| 1631 | 
            -
             | 
| 1632 | 
            -
              .lg\:grid-cols-2 {
         | 
| 1633 | 
            -
                grid-template-columns: repeat(2, minmax(0, 1fr));
         | 
| 1634 | 
            -
              }
         | 
| 1635 | 
            -
            }
         | 
| @@ -14,5 +14,50 @@ module Deployed | |
| 14 14 | 
             
                def initialize_deployed
         | 
| 15 15 | 
             
                  Deployed.setup!
         | 
| 16 16 | 
             
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def lock_file_path
         | 
| 19 | 
            +
                  Rails.root.join(Deployed::DIRECTORY, 'process.lock')
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def lock_process
         | 
| 23 | 
            +
                  File.open(lock_file_path, 'a') do |file|
         | 
| 24 | 
            +
                    file.puts(Deployed::CurrentExecution.child_pid)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def release_process
         | 
| 29 | 
            +
                  return unless File.exist?(lock_file_path)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  File.delete(lock_file_path)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def stored_pid
         | 
| 35 | 
            +
                  return false unless File.exist?(lock_file_path)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  value = File.read(lock_file_path).to_i
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  if value.is_a?(Integer)
         | 
| 40 | 
            +
                    value
         | 
| 41 | 
            +
                  else
         | 
| 42 | 
            +
                    false
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def process_running?
         | 
| 47 | 
            +
                  return false unless stored_pid
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  begin
         | 
| 50 | 
            +
                    # Send signal 0 to the process to check if it exists
         | 
| 51 | 
            +
                    Process.kill(0, stored_pid)
         | 
| 52 | 
            +
                    true
         | 
| 53 | 
            +
                  rescue Errno::ESRCH
         | 
| 54 | 
            +
                    false
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                helper_method :process_running?
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def current_log_file
         | 
| 60 | 
            +
                  Rails.root.join(Deployed::DIRECTORY, 'deployments/current.log')
         | 
| 61 | 
            +
                end
         | 
| 17 62 | 
             
              end
         | 
| 18 63 | 
             
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            module Deployed
         | 
| 2 | 
            +
              class LogOutputController < ApplicationController
         | 
| 3 | 
            +
                include ActionController::Live
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                before_action :set_headers
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def index
         | 
| 8 | 
            +
                  thread_exit_flag = false
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  thread = Thread.new do
         | 
| 11 | 
            +
                    File.open(current_log_file, 'r') do |file|
         | 
| 12 | 
            +
                      while true
         | 
| 13 | 
            +
                        IO.select([file])
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                        found_deployed = false
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                        file.each_line do |line|
         | 
| 18 | 
            +
                          # Check the exit flag
         | 
| 19 | 
            +
                          if thread_exit_flag
         | 
| 20 | 
            +
                            break
         | 
| 21 | 
            +
                          end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                          css_class = if line.include?('[Deployed]')
         | 
| 24 | 
            +
                            'text-slate-400'
         | 
| 25 | 
            +
                          else
         | 
| 26 | 
            +
                            'text-green-400'
         | 
| 27 | 
            +
                          end
         | 
| 28 | 
            +
                          sse.write("<div class='#{css_class}'>#{line.strip}</div>", event: 'message')
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                          if line.include?("[Deployed Rails] End")
         | 
| 31 | 
            +
                            found_deployed = true
         | 
| 32 | 
            +
                            break
         | 
| 33 | 
            +
                          end
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        if found_deployed || thread_exit_flag
         | 
| 37 | 
            +
                          break
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  begin
         | 
| 44 | 
            +
                    thread.join
         | 
| 45 | 
            +
                  rescue ActionController::Live::ClientDisconnected
         | 
| 46 | 
            +
                    logger.info 'Client Disconnected'
         | 
| 47 | 
            +
                  ensure
         | 
| 48 | 
            +
                    # Set the exit flag to true to signal the thread to exit
         | 
| 49 | 
            +
                    thread_exit_flag = true
         | 
| 50 | 
            +
                    sse.close
         | 
| 51 | 
            +
                    response.stream.close
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
             | 
| 56 | 
            +
                private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def set_headers
         | 
| 59 | 
            +
                  response.headers['Content-Type'] = 'text/event-stream'
         | 
| 60 | 
            +
                  response.headers['Last-Modified'] = Time.now.httpdate
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def sse
         | 
| 64 | 
            +
                  @sse ||= SSE.new(response.stream, event: 'Stream Started')
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| @@ -4,175 +4,44 @@ module Deployed | |
| 4 4 | 
             
              # Provides a centralized way to run all `kamal [command]` executions and streams to the browser
         | 
| 5 5 | 
             
              class RunController < ApplicationController
         | 
| 6 6 | 
             
                class ConcurrentProcessRunning < StandardError; end
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                include ActionController::Live
         | 
| 9 | 
            -
             | 
| 10 | 
            -
                before_action :set_headers
         | 
| 7 | 
            +
                skip_forgery_protection
         | 
| 11 8 |  | 
| 12 9 | 
             
                # Endpoint to execute the kamal command
         | 
| 13 10 | 
             
                def execute
         | 
| 14 | 
            -
                  if process_running?
         | 
| 15 | 
            -
             | 
| 16 | 
            -
                   | 
| 17 | 
            -
                    release_process
         | 
| 18 | 
            -
                  end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  sse.write(
         | 
| 21 | 
            -
                    "<div class='text-slate-400'>> <span class='text-slate-300 font-semibold'>kamal #{command}</span></div>",
         | 
| 22 | 
            -
                    event: 'message'
         | 
| 23 | 
            -
                  )
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                  read_io, write_io = IO.pipe
         | 
| 11 | 
            +
                  raise(ConcurrentProcessRunning) if process_running?
         | 
| 12 | 
            +
                  release_process if stored_pid
         | 
| 13 | 
            +
                  File.write(current_log_file, '')
         | 
| 26 14 |  | 
| 27 15 | 
             
                  # Fork a child process
         | 
| 28 16 | 
             
                  Deployed::CurrentExecution.child_pid = fork do
         | 
| 29 | 
            -
                     | 
| 30 | 
            -
                    $stdout.reopen(write_io)
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    # Execute the command
         | 
| 33 | 
            -
                    exec("kamal #{command}; echo \"[Deployed Rails] End transmission\"")
         | 
| 17 | 
            +
                    exec("bundle exec rake deployed:execute_and_log['#{command}']")
         | 
| 34 18 | 
             
                  end
         | 
| 35 19 |  | 
| 36 20 | 
             
                  lock_process
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  sse.write(
         | 
| 39 | 
            -
                    "<div class='text-slate-400' data-child-pid=\"#{Deployed::CurrentExecution.child_pid}\"></div>",
         | 
| 40 | 
            -
                    event: 'message'
         | 
| 41 | 
            -
                  )
         | 
| 42 | 
            -
             | 
| 43 | 
            -
                  write_io.close
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                  # Use a separate thread to read and stream the output
         | 
| 46 | 
            -
                  output_thread = Thread.new do
         | 
| 47 | 
            -
                    read_io.each_line do |line|
         | 
| 48 | 
            -
                      output = line.strip
         | 
| 49 | 
            -
                      output = output.gsub('49.13.91.176', '[redacted]')
         | 
| 50 | 
            -
                      text_color_class = 'text-green-400'
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                      # Hackish way of dealing with error messages at the moment
         | 
| 53 | 
            -
                      if output.include?('[31m')
         | 
| 54 | 
            -
                        text_color_class = 'text-red-500'
         | 
| 55 | 
            -
                        output.gsub!('[31m', '')
         | 
| 56 | 
            -
                        output.gsub!('[0m', '')
         | 
| 57 | 
            -
                      end
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                      sse.write("<div class='#{text_color_class}'>#{output}</div>", event: 'message')
         | 
| 60 | 
            -
                    end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                    # Ensure the response stream and the thread are closed properly
         | 
| 63 | 
            -
                    sse.close
         | 
| 64 | 
            -
                    response.stream.close
         | 
| 65 | 
            -
                  end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                  # Ensure that the thread is joined when the execution is complete
         | 
| 68 | 
            -
                  Process.wait
         | 
| 69 | 
            -
                  output_thread.join
         | 
| 21 | 
            +
                  render json: { message: 'OK' }
         | 
| 70 22 | 
             
                rescue ConcurrentProcessRunning
         | 
| 71 | 
            -
                   | 
| 72 | 
            -
                    "<div class='text-red-500'>Existing process running with PID: #{stored_pid}</div>",
         | 
| 73 | 
            -
                    event: 'message'
         | 
| 74 | 
            -
                  )
         | 
| 75 | 
            -
                  logger.info 'Existing process running'
         | 
| 76 | 
            -
                rescue ActionController::Live::ClientDisconnected
         | 
| 77 | 
            -
                  logger.info 'Client Disconnected'
         | 
| 78 | 
            -
                rescue IOError
         | 
| 79 | 
            -
                  logger.info 'IOError'
         | 
| 80 | 
            -
                ensure
         | 
| 81 | 
            -
                  sse.close
         | 
| 82 | 
            -
                  response.stream.close
         | 
| 83 | 
            -
                  release_process
         | 
| 23 | 
            +
                  render json: { message: 'EXISTING PROCESS' }
         | 
| 84 24 | 
             
                end
         | 
| 85 25 |  | 
| 86 26 | 
             
                # Endpoint to cancel currently running process
         | 
| 87 27 | 
             
                def cancel
         | 
| 28 | 
            +
                  pid = stored_pid
         | 
| 88 29 | 
             
                  if process_running?
         | 
| 89 30 | 
             
                    # If a process is running, get the PID and attempt to kill it
         | 
| 90 31 | 
             
                    begin
         | 
| 91 32 | 
             
                      Process.kill('TERM', stored_pid)
         | 
| 92 | 
            -
                      sse.write(
         | 
| 93 | 
            -
                        "<div class='text-yellow-400'>Cancelled the process with PID: #{stored_pid}</div>",
         | 
| 94 | 
            -
                        event: 'message'
         | 
| 95 | 
            -
                      )
         | 
| 96 | 
            -
                      release_process
         | 
| 97 33 | 
             
                    rescue Errno::ESRCH
         | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
                        event: 'message'
         | 
| 101 | 
            -
                      )
         | 
| 34 | 
            +
                    ensure
         | 
| 35 | 
            +
                      release_process
         | 
| 102 36 | 
             
                    end
         | 
| 103 | 
            -
                  else
         | 
| 104 | 
            -
                    sse.write(
         | 
| 105 | 
            -
                      "<div class='text-slate-400'>No process is currently running, nothing to cancel.</div>",
         | 
| 106 | 
            -
                      event: 'message'
         | 
| 107 | 
            -
                    )
         | 
| 108 37 | 
             
                  end
         | 
| 109 | 
            -
             | 
| 110 | 
            -
                  logger.info 'Client Disconnected'
         | 
| 111 | 
            -
                rescue IOError
         | 
| 112 | 
            -
                  logger.info 'IOError'
         | 
| 113 | 
            -
                ensure
         | 
| 114 | 
            -
                  sse.write(
         | 
| 115 | 
            -
                    '[Deployed Rails] End transmission',
         | 
| 116 | 
            -
                    event: 'message'
         | 
| 117 | 
            -
                  )
         | 
| 118 | 
            -
                  sse.close
         | 
| 119 | 
            -
                  response.stream.close
         | 
| 120 | 
            -
                  release_process
         | 
| 38 | 
            +
                  render json: { message: pid }
         | 
| 121 39 | 
             
                end
         | 
| 122 40 |  | 
| 123 41 | 
             
                private
         | 
| 124 42 |  | 
| 125 | 
            -
                def set_headers
         | 
| 126 | 
            -
                  response.headers['Content-Type'] = 'text/event-stream'
         | 
| 127 | 
            -
                  response.headers['Last-Modified'] = Time.now.httpdate
         | 
| 128 | 
            -
                end
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                def sse
         | 
| 131 | 
            -
                  @sse ||= SSE.new(response.stream, event: 'Stream Started')
         | 
| 132 | 
            -
                end
         | 
| 133 | 
            -
             | 
| 134 43 | 
             
                def command
         | 
| 135 44 | 
             
                  params[:command]
         | 
| 136 45 | 
             
                end
         | 
| 137 | 
            -
             | 
| 138 | 
            -
                def lock_file_path
         | 
| 139 | 
            -
                  Rails.root.join(Deployed::DIRECTORY, 'process.lock')
         | 
| 140 | 
            -
                end
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                def lock_process
         | 
| 143 | 
            -
                  File.open(lock_file_path, 'a') do |file|
         | 
| 144 | 
            -
                    file.puts(Deployed::CurrentExecution.child_pid)
         | 
| 145 | 
            -
                  end
         | 
| 146 | 
            -
                end
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                def release_process
         | 
| 149 | 
            -
                  return unless File.exist?(lock_file_path)
         | 
| 150 | 
            -
             | 
| 151 | 
            -
                  File.delete(lock_file_path)
         | 
| 152 | 
            -
                end
         | 
| 153 | 
            -
             | 
| 154 | 
            -
                def stored_pid
         | 
| 155 | 
            -
                  return false unless File.exist?(lock_file_path)
         | 
| 156 | 
            -
             | 
| 157 | 
            -
                  value = File.read(lock_file_path).to_i
         | 
| 158 | 
            -
             | 
| 159 | 
            -
                  if value.is_a?(Integer)
         | 
| 160 | 
            -
                    value
         | 
| 161 | 
            -
                  else
         | 
| 162 | 
            -
                    false
         | 
| 163 | 
            -
                  end
         | 
| 164 | 
            -
                end
         | 
| 165 | 
            -
             | 
| 166 | 
            -
                def process_running?
         | 
| 167 | 
            -
                  return false unless stored_pid
         | 
| 168 | 
            -
             | 
| 169 | 
            -
                  begin
         | 
| 170 | 
            -
                    # Send signal 0 to the process to check if it exists
         | 
| 171 | 
            -
                    Process.kill(0, stored_pid)
         | 
| 172 | 
            -
                    true
         | 
| 173 | 
            -
                  rescue Errno::ESRCH
         | 
| 174 | 
            -
                    false
         | 
| 175 | 
            -
                  end
         | 
| 176 | 
            -
                end
         | 
| 177 46 | 
             
              end
         | 
| 178 47 | 
             
            end
         | 
| @@ -10,7 +10,7 @@ | |
| 10 10 | 
             
              <script>
         | 
| 11 11 | 
             
                document.addEventListener('alpine:init', () => {
         | 
| 12 12 | 
             
                  Alpine.store('process', {
         | 
| 13 | 
            -
                    running:  | 
| 13 | 
            +
                    running: <%= process_running? %>,
         | 
| 14 14 | 
             
                    start() {
         | 
| 15 15 | 
             
                      this.running = true
         | 
| 16 16 | 
             
                    },
         | 
| @@ -47,14 +47,12 @@ | |
| 47 47 | 
             
                            DEPLOYED
         | 
| 48 48 | 
             
                          </div>
         | 
| 49 49 | 
             
                        </div>
         | 
| 50 | 
            +
                        <div>
         | 
| 51 | 
            +
                          <span class="text-base text-slate-400">v<%= Deployed::VERSION %></span>
         | 
| 52 | 
            +
                        </div>
         | 
| 50 53 | 
             
                      </div>
         | 
| 51 54 | 
             
                    </h1>
         | 
| 52 55 | 
             
                  </div>
         | 
| 53 | 
            -
                  <% if false %>
         | 
| 54 | 
            -
                  <div class="ml-4">
         | 
| 55 | 
            -
                    <%= render 'layouts/deployed/nav_menu' %>
         | 
| 56 | 
            -
                  </div>
         | 
| 57 | 
            -
                  <% end %>
         | 
| 58 56 | 
             
                </div>
         | 
| 59 57 | 
             
              </header>
         | 
| 60 58 | 
             
              <div class="h-screen flex flex-col pt-[56px]">
         | 
| @@ -86,5 +84,12 @@ | |
| 86 84 | 
             
              </div>
         | 
| 87 85 |  | 
| 88 86 | 
             
              <%= turbo_frame_tag('deployed-init', src: setup_path, target: '_top') if Deployed::Config.requires_init %>
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              <!-- If we refresh the page, we want to see the logs still piping... -->
         | 
| 89 | 
            +
              <script>
         | 
| 90 | 
            +
                setTimeout(() => {
         | 
| 91 | 
            +
                  <% if process_running? %>window.pipeLogs()<% end %>
         | 
| 92 | 
            +
                }, 1000)
         | 
| 93 | 
            +
              </script>
         | 
| 89 94 | 
             
            </body>
         | 
| 90 95 | 
             
            </html>
         | 
    
        data/config/routes.rb
    CHANGED
    
    | @@ -3,7 +3,8 @@ Deployed::Engine.routes.draw do | |
| 3 3 | 
             
              post 'setup', to: 'setup#create'
         | 
| 4 4 | 
             
              get 'config', to: 'config#show'
         | 
| 5 5 | 
             
              get 'git/uncommitted_check', to: 'git#uncommitted_check'
         | 
| 6 | 
            -
               | 
| 7 | 
            -
               | 
| 6 | 
            +
              post 'execute', to: 'run#execute'
         | 
| 7 | 
            +
              post 'cancel', to: 'run#cancel'
         | 
| 8 | 
            +
              get 'log_output', to: 'log_output#index'
         | 
| 8 9 | 
             
              root to: 'welcome#index'
         | 
| 9 10 | 
             
            end
         | 
    
        data/lib/deployed/version.rb
    CHANGED
    
    
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # lib/tasks/deployed.rake
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            namespace :deployed do
         | 
| 4 | 
            +
              desc "Execute a Kamal command and log its output"
         | 
| 5 | 
            +
              task :execute_and_log, [:command] => :environment do |task, args|
         | 
| 6 | 
            +
                command = args[:command]
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                unless command
         | 
| 9 | 
            +
                  puts "Please provide a Kamal command. Usage: rake deployed:execute_and_log[command]"
         | 
| 10 | 
            +
                  next
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                log_file = Rails.root.join(Deployed::DIRECTORY, 'deployments/current.log')
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                File.open(log_file, 'a') do |file|
         | 
| 16 | 
            +
                  IO.popen("kamal #{command}") do |io|
         | 
| 17 | 
            +
                    start_time = Time.now
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    file.puts("[Deployed] > kamal #{command}")
         | 
| 20 | 
            +
                    file.fsync
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    io.each_line do |line|
         | 
| 23 | 
            +
                      file.puts line
         | 
| 24 | 
            +
                      file.fsync  # Force data to be written to disk immediately
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                    end_time = Time.now
         | 
| 27 | 
            +
                    file.puts("[Deployed] Finished in #{end_time - start_time} seconds")
         | 
| 28 | 
            +
                    file.puts("[Deployed] End")
         | 
| 29 | 
            +
                    file.fsync
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    # Delete lockfile
         | 
| 32 | 
            +
                    File.delete(Rails.root.join(Deployed::DIRECTORY, 'process.lock'))
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: deployed
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Simon Chiu
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2023-10- | 
| 11 | 
            +
            date: 2023-10-26 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: kamal
         | 
| @@ -69,6 +69,7 @@ files: | |
| 69 69 | 
             
            - app/controllers/deployed/application_controller.rb
         | 
| 70 70 | 
             
            - app/controllers/deployed/config_controller.rb
         | 
| 71 71 | 
             
            - app/controllers/deployed/git_controller.rb
         | 
| 72 | 
            +
            - app/controllers/deployed/log_output_controller.rb
         | 
| 72 73 | 
             
            - app/controllers/deployed/run_controller.rb
         | 
| 73 74 | 
             
            - app/controllers/deployed/setup_controller.rb
         | 
| 74 75 | 
             
            - app/controllers/deployed/welcome_controller.rb
         | 
| @@ -79,13 +80,12 @@ files: | |
| 79 80 | 
             
            - app/views/deployed/git/uncommitted_check.html.erb
         | 
| 80 81 | 
             
            - app/views/deployed/setup/new.html.erb
         | 
| 81 82 | 
             
            - app/views/deployed/welcome/index.html.erb
         | 
| 82 | 
            -
            - app/views/layouts/deployed/_nav_menu.html.erb
         | 
| 83 83 | 
             
            - app/views/layouts/deployed/application.html.erb
         | 
| 84 84 | 
             
            - config/routes.rb
         | 
| 85 85 | 
             
            - lib/deployed.rb
         | 
| 86 86 | 
             
            - lib/deployed/engine.rb
         | 
| 87 87 | 
             
            - lib/deployed/version.rb
         | 
| 88 | 
            -
            - lib/tasks/ | 
| 88 | 
            +
            - lib/tasks/deployed_tasks.rake
         | 
| 89 89 | 
             
            homepage: https://github.com/geetfun/deployed
         | 
| 90 90 | 
             
            licenses:
         | 
| 91 91 | 
             
            - MIT
         | 
| @@ -93,7 +93,7 @@ metadata: | |
| 93 93 | 
             
              allowed_push_host: https://rubygems.org
         | 
| 94 94 | 
             
              homepage_uri: https://github.com/geetfun/deployed
         | 
| 95 95 | 
             
              source_code_uri: https://github.com/geetfun/deployed
         | 
| 96 | 
            -
              changelog_uri: https://github.com/geetfun/deployed
         | 
| 96 | 
            +
              changelog_uri: https://github.com/geetfun/deployed/blob/main/CHANGELOG.md
         | 
| 97 97 | 
             
            post_install_message:
         | 
| 98 98 | 
             
            rdoc_options: []
         | 
| 99 99 | 
             
            require_paths:
         | 
| @@ -1,54 +0,0 @@ | |
| 1 | 
            -
            <div class="relative">
         | 
| 2 | 
            -
              <button type="button" class="inline-flex items-center gap-x-1 text-sm font-semibold leading-6 text-slate-400" aria-expanded="false">
         | 
| 3 | 
            -
                <span>Resources</span>
         | 
| 4 | 
            -
                <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
         | 
| 5 | 
            -
                  <path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
         | 
| 6 | 
            -
                </svg>
         | 
| 7 | 
            -
              </button>
         | 
| 8 | 
            -
             | 
| 9 | 
            -
              <!--
         | 
| 10 | 
            -
                Flyout menu, show/hide based on flyout menu state.
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                Entering: "transition ease-out duration-200"
         | 
| 13 | 
            -
                  From: "opacity-0 translate-y-1"
         | 
| 14 | 
            -
                  To: "opacity-100 translate-y-0"
         | 
| 15 | 
            -
                Leaving: "transition ease-in duration-150"
         | 
| 16 | 
            -
                  From: "opacity-100 translate-y-0"
         | 
| 17 | 
            -
                  To: "opacity-0 translate-y-1"
         | 
| 18 | 
            -
              -->
         | 
| 19 | 
            -
              <div class="absolute z-10 mt-5 flex w-screen max-w-max left-[-225px] px-4">
         | 
| 20 | 
            -
                <div class="w-screen max-w-md flex-auto overflow-hidden rounded-3xl bg-white text-sm leading-6 shadow-lg ring-1 ring-gray-900/5 lg:max-w-2xl">
         | 
| 21 | 
            -
                  <div class="grid grid-cols-1 gap-x-6 gap-y-1 p-4 lg:grid-cols-2">
         | 
| 22 | 
            -
                    <div class="group relative flex gap-x-6 rounded-lg p-4 hover:bg-gray-50">
         | 
| 23 | 
            -
                      <div class="mt-1 flex h-11 w-11 flex-none items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white">
         | 
| 24 | 
            -
                        <svg class="h-6 w-6 text-gray-600 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
         | 
| 25 | 
            -
                          <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6a7.5 7.5 0 107.5 7.5h-7.5V6z" />
         | 
| 26 | 
            -
                          <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 10.5H21A7.5 7.5 0 0013.5 3v7.5z" />
         | 
| 27 | 
            -
                        </svg>
         | 
| 28 | 
            -
                      </div>
         | 
| 29 | 
            -
                      <div>
         | 
| 30 | 
            -
                        <a href="#" class="font-semibold text-gray-900">
         | 
| 31 | 
            -
                          Documentation
         | 
| 32 | 
            -
                          <span class="absolute inset-0"></span>
         | 
| 33 | 
            -
                        </a>
         | 
| 34 | 
            -
                        <p class="mt-1 text-gray-600">Check out our documentation</p>
         | 
| 35 | 
            -
                      </div>
         | 
| 36 | 
            -
                    </div>
         | 
| 37 | 
            -
                    <div class="group relative flex gap-x-6 rounded-lg p-4 hover:bg-gray-50">
         | 
| 38 | 
            -
                      <div class="mt-1 flex h-11 w-11 flex-none items-center justify-center rounded-lg bg-gray-50 group-hover:bg-white">
         | 
| 39 | 
            -
                        <svg class="h-6 w-6 text-gray-600 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
         | 
| 40 | 
            -
                          <path stroke-linecap="round" stroke-linejoin="round" d="M13.5 16.875h3.375m0 0h3.375m-3.375 0V13.5m0 3.375v3.375M6 10.5h2.25a2.25 2.25 0 002.25-2.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v2.25A2.25 2.25 0 006 10.5zm0 9.75h2.25A2.25 2.25 0 0010.5 18v-2.25a2.25 2.25 0 00-2.25-2.25H6a2.25 2.25 0 00-2.25 2.25V18A2.25 2.25 0 006 20.25zm9.75-9.75H18a2.25 2.25 0 002.25-2.25V6A2.25 2.25 0 0018 3.75h-2.25A2.25 2.25 0 0013.5 6v2.25a2.25 2.25 0 002.25 2.25z" />
         | 
| 41 | 
            -
                        </svg>
         | 
| 42 | 
            -
                      </div>
         | 
| 43 | 
            -
                      <div>
         | 
| 44 | 
            -
                        <a href="#" class="font-semibold text-gray-900">
         | 
| 45 | 
            -
                          Our GitHub Repository
         | 
| 46 | 
            -
                          <span class="absolute inset-0"></span>
         | 
| 47 | 
            -
                        </a>
         | 
| 48 | 
            -
                        <p class="mt-1 text-gray-600">Ipsum Lorem</p>
         | 
| 49 | 
            -
                      </div>
         | 
| 50 | 
            -
                    </div>
         | 
| 51 | 
            -
                  </div>
         | 
| 52 | 
            -
                </div>
         | 
| 53 | 
            -
              </div>
         | 
| 54 | 
            -
            </div>
         |