added processes

This commit is contained in:
Chris Zhu
2024-10-15 08:51:15 -07:00
parent 7cdbb70e9d
commit 70eea72e4f
20 changed files with 209 additions and 89 deletions

View File

@@ -3,6 +3,9 @@
- [ ] Onboarding flow
- [ ] we should have a feature to continuously poll stuff and figure out if they are still alive
- [ ] Healthchecks and whatnot
- [ ] Command to actually open a console with all the environment variables set up
- [ ] Finish the landing page
- [ ] Write the manifesto
## Setup

View File

@@ -7,4 +7,5 @@
@import "./forms.css";
@import "./weird_fixes.css";
@import "./tables.css";
@import "pacman.css";
@import "pacman.css";
@import "custom.css";

View File

@@ -0,0 +1,3 @@
.help-text {
@apply text-sm text-base-content/80 italic;
}

View File

@@ -1,4 +1,6 @@
class AddOns::LogsController < AddOns::BaseController
include LogColorsHelper
def index
@pods = get_pods_for_add_on(@add_on)
end

View File

@@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :authenticate_user!
layout :determine_layout

View File

@@ -1,22 +0,0 @@
class Projects::LogsController < Projects::BaseController
include LogColorsHelper
def index
@pods = get_pods_for_project(@project)
end
def show
client = K8::Client.new(@project.cluster.kubeconfig)
@logs = client.get_pod_log(params[:id], @project.name)
end
private
def get_pods_for_project(project)
# Get all pods for a given namespace
client = K8::Client.new(project.cluster.kubeconfig).client
pods = client.get_pods(namespace: project.name)
end
def set_cluster
@cluster = current_user.clusters.find(params[:cluster_id])
end
end

View File

@@ -0,0 +1,36 @@
class Projects::ProcessesController < Projects::BaseController
include LogColorsHelper
def index
@pods = get_pods_for_project(@project)
end
def create
kubectl = K8::Kubectl.from_project(@project)
pod = K8::Stateless::Pod.new(@project)
kubectl.apply_yaml(pod.to_yaml)
redirect_to project_processes_path(@project)
end
def show
client = K8::Client.new(@project.cluster.kubeconfig)
@logs = client.get_pod_log(params[:id], @project.name)
end
def destroy
client = K8::Client.from_project(@project)
client.delete_pod(params[:id], @project.name)
redirect_to project_processes_path(@project), notice: "Pod #{params[:id]} deleted"
end
private
def get_pods_for_project(project)
# Get all pods for a given namespace
client = K8::Client.from_project(project).client
pods = client.get_pods(namespace: project.name)
end
def set_cluster
@cluster = current_user.clusters.find(params[:cluster_id])
end
end

View File

@@ -79,7 +79,7 @@ class ProjectsController < ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_project
@project = Project.find(params[:id])
@project = current_user.projects.find(params[:id])
# Uncomment to authorize with Pundit
# authorize @project

View File

@@ -1,4 +1,6 @@
class StaticController < ApplicationController
skip_before_action :authenticate_user!
def index
end
end

View File

@@ -0,0 +1,13 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
console.log("processes controller connected")
}
showConnectionInstructions(event) {
event.preventDefault()
click_outside_modal.showModal()
}
}

View File

@@ -2,7 +2,7 @@ module K8
class Client
attr_reader :client
delegate :get_persistent_volume_claims, :get_services, :get_pods, :get_pod_log, to: :client
delegate :get_persistent_volume_claims, :get_services, :get_pods, :get_pod_log, :delete_pod, to: :client
def self.from_project(project)
new(project.cluster.kubeconfig)

View File

@@ -0,0 +1,8 @@
class K8::Stateless::Pod < K8::Base
attr_accessor :project, :id
def initialize(project)
@project = project
@id = SecureRandom.uuid[0..7]
end
end

View File

@@ -14,9 +14,9 @@
Environment
</div>
<% end %>
<%= link_to project_logs_path(project), class: "tab hover:bg-base-content/15 #{'tab-active' if current_page?(project_logs_path(project))}" do %>
<%= link_to project_processes_path(project), class: "tab hover:bg-base-content/15 #{'tab-active' if current_page?(project_processes_path(project))}" do %>
<div class="flex items-center gap-2">
Logs
Processes
</div>
<% end %>
<%= link_to project_metrics_url(project), class: "tab hover:bg-base-content/15 #{'tab-active' if current_page?(project_metrics_url(project))}" do %>

View File

@@ -1,47 +0,0 @@
<%= project_layout(@project) do %>
<%= turbo_frame_tag "pod_logs" do %>
<% if @pods.empty? %>
<div>
<p class="text-gray-500">Nothing running for this project</p>
</div>
<% else %>
<table class="table mt-2 rounded-box" data-component="table">
<thead>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Pod Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Status
</span>
</th>
</tr>
</thead>
<tbody>
<% @pods.each do |pod| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate">
<div class="font-medium">
<%= pod.metadata.name %>
</div>
</div>
</td>
<td>
<div class="font-medium">
<%= pod.status.phase %>
</div>
</td>
<td>
<div class="font-medium">
<%= link_to "Show Logs", project_log_path(@project, pod.metadata.name) %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
<% end %>

View File

@@ -0,0 +1,24 @@
<dialog aria-label="Modal" class="modal" id="click_outside_modal">
<div class="modal-box w-11/12 max-w-5xl bg-base-300">
<form method="dialog">
<button aria-label="Close modal" class="btn btn-circle btn-ghost btn-sm absolute right-2 top-2">
<iconify-icon icon="lucide:x" height="16"></iconify-icon>
</button>
</form>
<div class="mb-8 w-full text-xl font-bold">Run this command to connect to your pod</div>
<div class="my-4">
<h4 class="text-lg font-bold">Step 1: Download Kubeconfig</h4>
<%= link_to "Download Kubeconfig", download_kubeconfig_cluster_path(@project.cluster), class: "btn btn-sm btn-primary btn-outline", target: "_blank" %>
</div>
<div class="my-4">
<h4 class="text-lg font-bold">Step 2: Run the command</h4>
<div role="tooltip" data-tip="Click to copy" class="tooltip tooltip-secondary">
<code class="hover:cursor-pointer"><pre>KUBECONFIG=/path/to/kubeconfig kubectl exec -it {pod_name} -- /bin/bash</pre></code>
</div>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>

View File

@@ -0,0 +1,85 @@
<%= project_layout(@project) do %>
<div class="mb-4" data-controller="processes">
<h3 class="text-lg font-medium">Processes</h3>
<%= button_to project_processes_path(@project), method: :post, class: "btn btn-sm btn-primary" do %>
<iconify-icon icon="ic:baseline-plus"></iconify-icon>
Create One-Off Pod
<% end %>
<div class="help-text mt-2">
One-off pods are useful for running a single instance of a pod for a short period of time, so you can test something or run a command that doesn't need to be permanent.
</div>
</div>
<%= turbo_frame_tag "pod_logs" do %>
<% if @pods.empty? %>
<div>
<p class="text-gray-500">Nothing running for this project</p>
</div>
<% else %>
<table class="table mt-2 rounded-box" data-component="table">
<thead>
<tr>
<th>
<span class="text-sm font-medium text-base-content/80">Pod Name</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Status
</span>
</th>
<th>
<span class="text-sm font-medium text-base-content/80">
Created At
</span>
</th>
</tr>
</thead>
<tbody>
<% @pods.each do |pod| %>
<tr class="cursor-pointer hover:bg-base-200/40">
<td>
<div class="flex items-center space-x-3 truncate">
<div class="font-medium">
<%= pod.metadata.name %>
</div>
</div>
</td>
<td>
<div class="font-medium">
<%= pod.status.phase %>
</div>
</td>
<td>
<div class="font-medium">
<%= Time.parse(pod.metadata.creationTimestamp).to_formatted_s(:short) %>
</div>
</td>
<td>
<div class="flex items-center space-x-2">
<div class="font-medium">
<%= link_to "Show Logs", project_process_path(@project, pod.metadata.name), class: "btn btn-sm btn-primary btn-outline" %>
</div>
<div class="font-medium">
<button class="btn btn-sm btn-info btn-outline" data-action="processes#showConnectionInstructions">Connect</button>
</div>
<div class="font-medium">
<% if pod.status.phase != "Running" || pod.metadata.labels.oneoff %>
<%= link_to "Delete", project_process_path(@project, pod.metadata.name), method: :delete, class: "btn btn-sm btn-error btn-outline" %>
<% else %>
<div role="tooltip" data-tip="Be careful when deleting running pods, it can cause downtime for your project" class="tooltip tooltip-secondary">
<%= link_to "Delete", project_process_path(@project, pod.metadata.name), method: :delete, class: "btn btn-sm btn-error btn-outline" %>
</div>
<% end %>
</div>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
<% end %>
<% end %>
<%= render "projects/processes/connect_modal" %>

View File

@@ -8,7 +8,6 @@ Rails.application.routes.draw do
get "/privacy", to: "static#privacy"
get "/terms", to: "static#terms"
authenticated :user do
root to: "projects#index", as: :user_root
# Alternate route to use if logged in users should still see public root
@@ -25,7 +24,7 @@ Rails.application.routes.draw do
collection do
get "/:project_id/deployments", to: "projects/deployments#index", as: :root
end
resources :logs, only: %i[index show], module: :projects
resources :processes, only: %i[index show create destroy], module: :projects
resources :services, only: %i[index new create destroy update], module: :projects do
resources :domains, only: %i[create destroy], module: :services
end

View File

@@ -4,7 +4,7 @@
//
// `yarn build` - Build JavaScript and exit
// `yarn build --watch` - Rebuild JavaScript on change
// `yarn build --reload` - Reloads page when views, JavaScript, or stylesheets change
// `yarn build --reload` - Reloads page when views, JavaScript, or stylesheets change. Requires a PORT to listen on. Defaults to 3200 but can be specified with PORT env var
//
// Minify is enabled when "RAILS_ENV=production"
// Sourcemaps are enabled in non-production environments
@@ -18,14 +18,13 @@ import { setTimeout } from "timers/promises"
const clients = []
const entryPoints = [
"application.js"
"application.js",
]
const watchDirectories = [
"./app/javascript/*.js",
"./app/javascript/**/*.js",
"./app/javascript/**/**/*.js",
"./app/views/**/*.erb",
"./app/views/**/*.html.erb",
"./app/assets/builds/**/*.css", // Wait for cssbundling changes
"./config/locales/**/*.yml",
]
const config = {
absWorkingDir: path.join(process.cwd(), "app/javascript"),
@@ -39,11 +38,18 @@ const config = {
async function buildAndReload() {
// Foreman & Overmind assign a separate PORT for each process
const port = parseInt(process.env.PORT || 6543)
const port = parseInt(process.env.PORT || 3200)
console.log(`Esbuild is listening on port ${port}`)
const context = await esbuild.context({
...config,
banner: {
js: ` (() => new EventSource("http://localhost:${port}").onmessage = () => location.reload())();`,
js: `
(() => {
if (typeof EventSource !== 'undefined') {
new EventSource("http://localhost:${port}").onmessage = () => location.reload()
}
})();
`,
}
})

View File

@@ -1,14 +1,20 @@
apiVersion: v1
kind: Pod
metadata:
name: <%= name %>-pod
namespace: default
name: <%= project.name %>-run-<%= id %>
namespace: <%= project.name %>
labels:
oneoff: 'true'
spec:
restartPolicy: Never
containers:
- image: <%= container %>
- name: <%= project.name %>
image: <%= project.container_registry_url %>
command:
- sleep
- "3600"
imagePullPolicy: Always
name: <%= container %>
restartPolicy: Always
envFrom:
- configMapRef:
name: <%= project.name %>
imagePullSecrets:
- name: dockerconfigjson-github-com