mirror of https://github.com/coder/coder.git
829 lines
25 KiB
TypeScript
829 lines
25 KiB
TypeScript
import { getErrorMessage } from "api/errors"
|
|
import dayjs from "dayjs"
|
|
import { workspaceScheduleBannerMachine } from "xServices/workspaceSchedule/workspaceScheduleBannerXService"
|
|
import { assign, createMachine, send } from "xstate"
|
|
import * as API from "../../api/api"
|
|
import * as Types from "../../api/types"
|
|
import * as TypesGen from "../../api/typesGenerated"
|
|
import {
|
|
displayError,
|
|
displaySuccess,
|
|
} from "../../components/GlobalSnackbar/utils"
|
|
import { AxiosError } from "axios"
|
|
|
|
const latestBuild = (builds: TypesGen.WorkspaceBuild[]) => {
|
|
// Cloning builds to not change the origin object with the sort()
|
|
return [...builds].sort((a, b) => {
|
|
return new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime()
|
|
})[0]
|
|
}
|
|
|
|
const moreBuildsAvailable = (
|
|
context: WorkspaceContext,
|
|
event: {
|
|
type: "REFRESH_TIMELINE"
|
|
checkRefresh?: boolean
|
|
data?: TypesGen.ServerSentEvent["data"]
|
|
},
|
|
) => {
|
|
// No need to refresh the timeline if it is not loaded
|
|
if (!context.builds) {
|
|
return false
|
|
}
|
|
|
|
if (!event.checkRefresh) {
|
|
return true
|
|
}
|
|
|
|
// After we refresh a workspace, we want to check if the latest
|
|
// build was updated before refreshing the timeline so as to not over fetch the builds
|
|
const latestBuildInTimeline = latestBuild(context.builds)
|
|
return event.data.latest_build.updated_at !== latestBuildInTimeline.updated_at
|
|
}
|
|
|
|
const Language = {
|
|
getTemplateWarning:
|
|
"Error updating workspace: latest template could not be fetched.",
|
|
getTemplateParametersWarning:
|
|
"Error updating workspace: template parameters could not be fetched.",
|
|
buildError: "Workspace action failed.",
|
|
}
|
|
|
|
type Permissions = Record<keyof ReturnType<typeof permissionsToCheck>, boolean>
|
|
|
|
export interface WorkspaceContext {
|
|
// Initial data
|
|
username: string
|
|
workspaceName: string
|
|
// our server side events instance
|
|
eventSource?: EventSource
|
|
workspace?: TypesGen.Workspace
|
|
template?: TypesGen.Template
|
|
build?: TypesGen.WorkspaceBuild
|
|
getWorkspaceError?: AxiosError
|
|
getTemplateWarning: Error | unknown
|
|
getTemplateParametersWarning: Error | unknown
|
|
// Builds
|
|
builds?: TypesGen.WorkspaceBuild[]
|
|
getBuildsError?: Error | unknown
|
|
missedParameters?: TypesGen.TemplateVersionParameter[]
|
|
// error creating a new WorkspaceBuild
|
|
buildError?: Error | unknown
|
|
cancellationMessage?: Types.Message
|
|
cancellationError?: Error | unknown
|
|
// permissions
|
|
permissions?: Permissions
|
|
checkPermissionsError?: Error | unknown
|
|
// debug
|
|
createBuildLogLevel?: TypesGen.CreateWorkspaceBuildRequest["log_level"]
|
|
// SSH Config
|
|
sshPrefix?: string
|
|
// Change version
|
|
templateVersionIdToChange?: TypesGen.TemplateVersion["id"]
|
|
}
|
|
|
|
export type WorkspaceEvent =
|
|
| { type: "REFRESH_WORKSPACE"; data: TypesGen.ServerSentEvent["data"] }
|
|
| { type: "START" }
|
|
| { type: "STOP" }
|
|
| { type: "ASK_DELETE" }
|
|
| { type: "DELETE" }
|
|
| { type: "CANCEL_DELETE" }
|
|
| { type: "UPDATE"; buildParameters?: TypesGen.WorkspaceBuildParameter[] }
|
|
| {
|
|
type: "CHANGE_VERSION"
|
|
templateVersionId: TypesGen.TemplateVersion["id"]
|
|
buildParameters?: TypesGen.WorkspaceBuildParameter[]
|
|
}
|
|
| { type: "CANCEL" }
|
|
| {
|
|
type: "REFRESH_TIMELINE"
|
|
checkRefresh?: boolean
|
|
data?: TypesGen.ServerSentEvent["data"]
|
|
}
|
|
| { type: "EVENT_SOURCE_ERROR"; error: Error | unknown }
|
|
| { type: "INCREASE_DEADLINE"; hours: number }
|
|
| { type: "DECREASE_DEADLINE"; hours: number }
|
|
| { type: "RETRY_BUILD" }
|
|
|
|
export const checks = {
|
|
readWorkspace: "readWorkspace",
|
|
updateWorkspace: "updateWorkspace",
|
|
updateTemplate: "updateTemplate",
|
|
} as const
|
|
|
|
const permissionsToCheck = (
|
|
workspace: TypesGen.Workspace,
|
|
template: TypesGen.Template,
|
|
) =>
|
|
({
|
|
[checks.readWorkspace]: {
|
|
object: {
|
|
resource_type: "workspace",
|
|
resource_id: workspace.id,
|
|
owner_id: workspace.owner_id,
|
|
},
|
|
action: "read",
|
|
},
|
|
[checks.updateWorkspace]: {
|
|
object: {
|
|
resource_type: "workspace",
|
|
resource_id: workspace.id,
|
|
owner_id: workspace.owner_id,
|
|
},
|
|
action: "update",
|
|
},
|
|
[checks.updateTemplate]: {
|
|
object: {
|
|
resource_type: "template",
|
|
resource_id: template.id,
|
|
},
|
|
action: "update",
|
|
},
|
|
} as const)
|
|
|
|
export const workspaceMachine = createMachine(
|
|
{
|
|
id: "workspaceState",
|
|
predictableActionArguments: true,
|
|
tsTypes: {} as import("./workspaceXService.typegen").Typegen0,
|
|
schema: {
|
|
context: {} as WorkspaceContext,
|
|
events: {} as WorkspaceEvent,
|
|
services: {} as {
|
|
getWorkspace: {
|
|
data: TypesGen.Workspace
|
|
}
|
|
getTemplate: {
|
|
data: TypesGen.Template
|
|
}
|
|
getTemplateParameters: {
|
|
data: TypesGen.TemplateVersionParameter[]
|
|
}
|
|
updateWorkspace: {
|
|
data: TypesGen.WorkspaceBuild
|
|
}
|
|
changeWorkspaceVersion: {
|
|
data: TypesGen.WorkspaceBuild
|
|
}
|
|
startWorkspace: {
|
|
data: TypesGen.WorkspaceBuild
|
|
}
|
|
stopWorkspace: {
|
|
data: TypesGen.WorkspaceBuild
|
|
}
|
|
deleteWorkspace: {
|
|
data: TypesGen.WorkspaceBuild
|
|
}
|
|
cancelWorkspace: {
|
|
data: Types.Message
|
|
}
|
|
listening: {
|
|
data: TypesGen.ServerSentEvent
|
|
}
|
|
getBuilds: {
|
|
data: TypesGen.WorkspaceBuild[]
|
|
}
|
|
checkPermissions: {
|
|
data: TypesGen.AuthorizationResponse
|
|
}
|
|
getSSHPrefix: {
|
|
data: TypesGen.SSHConfigResponse
|
|
}
|
|
},
|
|
},
|
|
initial: "gettingWorkspace",
|
|
states: {
|
|
gettingWorkspace: {
|
|
entry: ["clearContext"],
|
|
invoke: {
|
|
src: "getWorkspace",
|
|
id: "getWorkspace",
|
|
onDone: [
|
|
{
|
|
actions: ["assignWorkspace", "clearGetWorkspaceError"],
|
|
target: "gettingTemplate",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignGetWorkspaceError",
|
|
target: "error",
|
|
},
|
|
],
|
|
},
|
|
tags: "loading",
|
|
},
|
|
gettingTemplate: {
|
|
invoke: {
|
|
src: "getTemplate",
|
|
id: "getTemplate",
|
|
onDone: [
|
|
{
|
|
actions: ["assignTemplate", "clearGetTemplateWarning"],
|
|
target: "gettingPermissions",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: [
|
|
"assignGetTemplateWarning",
|
|
"displayGetTemplateWarning",
|
|
],
|
|
target: "error",
|
|
},
|
|
],
|
|
},
|
|
tags: "loading",
|
|
},
|
|
gettingPermissions: {
|
|
invoke: {
|
|
src: "checkPermissions",
|
|
id: "checkPermissions",
|
|
onDone: [
|
|
{
|
|
actions: ["assignPermissions", "clearGetPermissionsError"],
|
|
target: "ready",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignGetPermissionsError",
|
|
target: "error",
|
|
},
|
|
],
|
|
},
|
|
tags: "loading",
|
|
},
|
|
ready: {
|
|
type: "parallel",
|
|
states: {
|
|
listening: {
|
|
initial: "gettingEvents",
|
|
states: {
|
|
gettingEvents: {
|
|
entry: ["initializeEventSource"],
|
|
exit: "closeEventSource",
|
|
invoke: {
|
|
src: "listening",
|
|
id: "listening",
|
|
},
|
|
on: {
|
|
REFRESH_WORKSPACE: {
|
|
actions: ["refreshWorkspace"],
|
|
},
|
|
EVENT_SOURCE_ERROR: {
|
|
target: "error",
|
|
},
|
|
},
|
|
},
|
|
error: {
|
|
entry: "logWatchWorkspaceWarning",
|
|
after: {
|
|
"2000": {
|
|
target: "gettingEvents",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
build: {
|
|
initial: "idle",
|
|
states: {
|
|
idle: {
|
|
on: {
|
|
START: "requestingStart",
|
|
STOP: "requestingStop",
|
|
ASK_DELETE: "askingDelete",
|
|
UPDATE: "requestingUpdate",
|
|
CHANGE_VERSION: {
|
|
target: "requestingChangeVersion",
|
|
actions: ["assignTemplateVersionIdToChange"],
|
|
},
|
|
CANCEL: "requestingCancel",
|
|
RETRY_BUILD: [
|
|
{
|
|
target: "requestingStart",
|
|
cond: "lastBuildWasStarting",
|
|
actions: ["enableDebugMode"],
|
|
},
|
|
{
|
|
target: "requestingStop",
|
|
cond: "lastBuildWasStopping",
|
|
actions: ["enableDebugMode"],
|
|
},
|
|
{
|
|
target: "requestingDelete",
|
|
cond: "lastBuildWasDeleting",
|
|
actions: ["enableDebugMode"],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
askingDelete: {
|
|
on: {
|
|
DELETE: {
|
|
target: "requestingDelete",
|
|
},
|
|
CANCEL_DELETE: {
|
|
target: "idle",
|
|
},
|
|
},
|
|
},
|
|
requestingUpdate: {
|
|
entry: ["clearBuildError"],
|
|
invoke: {
|
|
src: "updateWorkspace",
|
|
onDone: {
|
|
target: "idle",
|
|
actions: ["assignBuild"],
|
|
},
|
|
onError: [
|
|
{
|
|
target: "askingForMissedBuildParameters",
|
|
cond: "isMissingBuildParameterError",
|
|
actions: ["assignMissedParameters"],
|
|
},
|
|
{
|
|
target: "idle",
|
|
actions: ["assignBuildError"],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
requestingChangeVersion: {
|
|
entry: ["clearBuildError"],
|
|
invoke: {
|
|
src: "changeWorkspaceVersion",
|
|
onDone: {
|
|
target: "idle",
|
|
actions: ["assignBuild", "clearTemplateVersionIdToChange"],
|
|
},
|
|
onError: [
|
|
{
|
|
target: "askingForMissedBuildParameters",
|
|
cond: "isMissingBuildParameterError",
|
|
actions: ["assignMissedParameters"],
|
|
},
|
|
{
|
|
target: "idle",
|
|
actions: ["assignBuildError"],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
askingForMissedBuildParameters: {
|
|
on: {
|
|
CANCEL: "idle",
|
|
UPDATE: [
|
|
{
|
|
target: "requestingChangeVersion",
|
|
cond: "isChangingVersion",
|
|
},
|
|
{ target: "requestingUpdate" },
|
|
],
|
|
},
|
|
},
|
|
requestingStart: {
|
|
entry: ["clearBuildError"],
|
|
invoke: {
|
|
src: "startWorkspace",
|
|
id: "startWorkspace",
|
|
onDone: [
|
|
{
|
|
actions: ["assignBuild", "disableDebugMode"],
|
|
target: "idle",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignBuildError",
|
|
target: "idle",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
requestingStop: {
|
|
entry: ["clearBuildError"],
|
|
invoke: {
|
|
src: "stopWorkspace",
|
|
id: "stopWorkspace",
|
|
onDone: [
|
|
{
|
|
actions: ["assignBuild", "disableDebugMode"],
|
|
target: "idle",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignBuildError",
|
|
target: "idle",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
requestingDelete: {
|
|
entry: ["clearBuildError"],
|
|
invoke: {
|
|
src: "deleteWorkspace",
|
|
id: "deleteWorkspace",
|
|
onDone: [
|
|
{
|
|
actions: ["assignBuild", "disableDebugMode"],
|
|
target: "idle",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignBuildError",
|
|
target: "idle",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
requestingCancel: {
|
|
entry: ["clearCancellationMessage", "clearCancellationError"],
|
|
invoke: {
|
|
src: "cancelWorkspace",
|
|
id: "cancelWorkspace",
|
|
onDone: [
|
|
{
|
|
actions: [
|
|
"assignCancellationMessage",
|
|
"displayCancellationMessage",
|
|
],
|
|
target: "idle",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignCancellationError",
|
|
target: "idle",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
timeline: {
|
|
initial: "gettingBuilds",
|
|
states: {
|
|
gettingBuilds: {
|
|
invoke: {
|
|
src: "getBuilds",
|
|
onDone: [
|
|
{
|
|
actions: ["assignBuilds", "clearGetBuildsError"],
|
|
target: "loadedBuilds",
|
|
},
|
|
],
|
|
onError: [
|
|
{
|
|
actions: "assignGetBuildsError",
|
|
target: "loadedBuilds",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
loadedBuilds: {
|
|
on: {
|
|
REFRESH_TIMELINE: {
|
|
target: "#workspaceState.ready.timeline.gettingBuilds",
|
|
cond: "moreBuildsAvailable",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
sshConfig: {
|
|
initial: "gettingSshConfig",
|
|
states: {
|
|
gettingSshConfig: {
|
|
invoke: {
|
|
src: "getSSHPrefix",
|
|
onDone: {
|
|
target: "success",
|
|
actions: ["assignSSHPrefix"],
|
|
},
|
|
onError: {
|
|
target: "error",
|
|
actions: ["displaySSHPrefixError"],
|
|
},
|
|
},
|
|
},
|
|
error: {
|
|
type: "final",
|
|
},
|
|
success: {
|
|
type: "final",
|
|
},
|
|
},
|
|
},
|
|
schedule: {
|
|
invoke: {
|
|
id: "scheduleBannerMachine",
|
|
src: "scheduleBannerMachine",
|
|
data: {
|
|
workspace: (context: WorkspaceContext) => context.workspace,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
error: {
|
|
type: "final",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
actions: {
|
|
// Clear data about an old workspace when looking at a new one
|
|
clearContext: () =>
|
|
assign({
|
|
workspace: undefined,
|
|
template: undefined,
|
|
build: undefined,
|
|
permissions: undefined,
|
|
eventSource: undefined,
|
|
}),
|
|
assignWorkspace: assign({
|
|
workspace: (_, event) => event.data,
|
|
}),
|
|
assignGetWorkspaceError: assign({
|
|
getWorkspaceError: (_, event) => event.data as AxiosError,
|
|
}),
|
|
clearGetWorkspaceError: (context) =>
|
|
assign({ ...context, getWorkspaceError: undefined }),
|
|
assignTemplate: assign({
|
|
template: (_, event) => event.data,
|
|
}),
|
|
assignPermissions: assign({
|
|
// Setting event.data as Permissions to be more stricted. So we know
|
|
// what permissions we asked for.
|
|
permissions: (_, event) => event.data as Permissions,
|
|
}),
|
|
assignGetPermissionsError: assign({
|
|
checkPermissionsError: (_, event) => event.data,
|
|
}),
|
|
clearGetPermissionsError: assign({
|
|
checkPermissionsError: (_) => undefined,
|
|
}),
|
|
assignBuild: assign({
|
|
build: (_, event) => event.data,
|
|
}),
|
|
assignBuildError: assign({
|
|
buildError: (_, event) => event.data,
|
|
}),
|
|
clearBuildError: assign({
|
|
buildError: (_) => undefined,
|
|
}),
|
|
assignCancellationMessage: assign({
|
|
cancellationMessage: (_, event) => event.data,
|
|
}),
|
|
clearCancellationMessage: assign({
|
|
cancellationMessage: (_) => undefined,
|
|
}),
|
|
displayCancellationMessage: (context) => {
|
|
if (context.cancellationMessage) {
|
|
displaySuccess(context.cancellationMessage.message)
|
|
}
|
|
},
|
|
assignCancellationError: assign({
|
|
cancellationError: (_, event) => event.data,
|
|
}),
|
|
clearCancellationError: assign({
|
|
cancellationError: (_) => undefined,
|
|
}),
|
|
// SSE related actions
|
|
// open a new EventSource so we can stream SSE
|
|
initializeEventSource: assign({
|
|
eventSource: (context) =>
|
|
context.workspace && API.watchWorkspace(context.workspace.id),
|
|
}),
|
|
closeEventSource: (context) =>
|
|
context.eventSource && context.eventSource.close(),
|
|
refreshWorkspace: assign({
|
|
workspace: (_, event) => event.data,
|
|
}),
|
|
logWatchWorkspaceWarning: (_, event) => {
|
|
console.error("Watch workspace error:", event)
|
|
},
|
|
assignGetTemplateWarning: assign({
|
|
getTemplateWarning: (_, event) => event.data,
|
|
}),
|
|
displayGetTemplateWarning: () => {
|
|
displayError(Language.getTemplateWarning)
|
|
},
|
|
clearGetTemplateWarning: assign({
|
|
getTemplateWarning: (_) => undefined,
|
|
}),
|
|
// Timeline
|
|
assignBuilds: assign({
|
|
builds: (_, event) => event.data,
|
|
}),
|
|
assignGetBuildsError: assign({
|
|
getBuildsError: (_, event) => event.data,
|
|
}),
|
|
clearGetBuildsError: assign({
|
|
getBuildsError: (_) => undefined,
|
|
}),
|
|
// SSH
|
|
assignSSHPrefix: assign({
|
|
sshPrefix: (_, { data }) => data.hostname_prefix,
|
|
}),
|
|
displaySSHPrefixError: (_, { data }) => {
|
|
const message = getErrorMessage(
|
|
data,
|
|
"Error getting the deployment ssh configuration.",
|
|
)
|
|
displayError(message)
|
|
},
|
|
|
|
assignMissedParameters: assign({
|
|
missedParameters: (_, { data }) => {
|
|
if (!(data instanceof API.MissingBuildParameters)) {
|
|
throw new Error("data is not a MissingBuildParameters error")
|
|
}
|
|
return data.parameters
|
|
},
|
|
}),
|
|
// Debug mode when build fails
|
|
enableDebugMode: assign({ createBuildLogLevel: (_) => "debug" as const }),
|
|
disableDebugMode: assign({ createBuildLogLevel: (_) => undefined }),
|
|
// Change version
|
|
assignTemplateVersionIdToChange: assign({
|
|
templateVersionIdToChange: (_, { templateVersionId }) =>
|
|
templateVersionId,
|
|
}),
|
|
clearTemplateVersionIdToChange: assign({
|
|
templateVersionIdToChange: (_) => undefined,
|
|
}),
|
|
},
|
|
guards: {
|
|
moreBuildsAvailable,
|
|
isMissingBuildParameterError: (_, { data }) => {
|
|
return data instanceof API.MissingBuildParameters
|
|
},
|
|
lastBuildWasStarting: ({ workspace }) => {
|
|
return workspace?.latest_build.transition === "start"
|
|
},
|
|
lastBuildWasStopping: ({ workspace }) => {
|
|
return workspace?.latest_build.transition === "stop"
|
|
},
|
|
lastBuildWasDeleting: ({ workspace }) => {
|
|
return workspace?.latest_build.transition === "delete"
|
|
},
|
|
isChangingVersion: ({ templateVersionIdToChange }) =>
|
|
Boolean(templateVersionIdToChange),
|
|
},
|
|
services: {
|
|
getWorkspace: async ({ username, workspaceName }) => {
|
|
return await API.getWorkspaceByOwnerAndName(username, workspaceName, {
|
|
include_deleted: true,
|
|
})
|
|
},
|
|
getTemplate: async (context) => {
|
|
if (context.workspace) {
|
|
return await API.getTemplate(context.workspace.template_id)
|
|
} else {
|
|
throw Error("Cannot get template without workspace")
|
|
}
|
|
},
|
|
updateWorkspace:
|
|
({ workspace }, { buildParameters }) =>
|
|
async (send) => {
|
|
if (!workspace) {
|
|
throw new Error("Workspace is not set")
|
|
}
|
|
const build = await API.updateWorkspace(workspace, buildParameters)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return build
|
|
},
|
|
changeWorkspaceVersion:
|
|
({ workspace, templateVersionIdToChange }, { buildParameters }) =>
|
|
async (send) => {
|
|
if (!workspace) {
|
|
throw new Error("Workspace is not set")
|
|
}
|
|
if (!templateVersionIdToChange) {
|
|
throw new Error("Template version id to change is not set")
|
|
}
|
|
const build = await API.changeWorkspaceVersion(
|
|
workspace,
|
|
templateVersionIdToChange,
|
|
buildParameters,
|
|
)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return build
|
|
},
|
|
startWorkspace: (context) => async (send) => {
|
|
if (context.workspace) {
|
|
const startWorkspacePromise = await API.startWorkspace(
|
|
context.workspace.id,
|
|
context.workspace.latest_build.template_version_id,
|
|
context.createBuildLogLevel,
|
|
)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return startWorkspacePromise
|
|
} else {
|
|
throw Error("Cannot start workspace without workspace id")
|
|
}
|
|
},
|
|
stopWorkspace: (context) => async (send) => {
|
|
if (context.workspace) {
|
|
const stopWorkspacePromise = await API.stopWorkspace(
|
|
context.workspace.id,
|
|
context.createBuildLogLevel,
|
|
)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return stopWorkspacePromise
|
|
} else {
|
|
throw Error("Cannot stop workspace without workspace id")
|
|
}
|
|
},
|
|
deleteWorkspace: async (context) => {
|
|
if (context.workspace) {
|
|
const deleteWorkspacePromise = await API.deleteWorkspace(
|
|
context.workspace.id,
|
|
context.createBuildLogLevel,
|
|
)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return deleteWorkspacePromise
|
|
} else {
|
|
throw Error("Cannot delete workspace without workspace id")
|
|
}
|
|
},
|
|
cancelWorkspace: (context) => async (send) => {
|
|
if (context.workspace) {
|
|
const cancelWorkspacePromise = await API.cancelWorkspaceBuild(
|
|
context.workspace.latest_build.id,
|
|
)
|
|
send({ type: "REFRESH_TIMELINE" })
|
|
return cancelWorkspacePromise
|
|
} else {
|
|
throw Error("Cannot cancel workspace without build id")
|
|
}
|
|
},
|
|
listening: (context) => (send) => {
|
|
if (!context.eventSource) {
|
|
send({ type: "EVENT_SOURCE_ERROR", error: "error initializing sse" })
|
|
return
|
|
}
|
|
|
|
context.eventSource.addEventListener("data", (event) => {
|
|
// refresh our workspace with each SSE
|
|
send({ type: "REFRESH_WORKSPACE", data: JSON.parse(event.data) })
|
|
// refresh our timeline
|
|
send({
|
|
type: "REFRESH_TIMELINE",
|
|
checkRefresh: true,
|
|
data: JSON.parse(event.data),
|
|
})
|
|
// refresh
|
|
})
|
|
|
|
// handle any error events returned by our sse
|
|
context.eventSource.addEventListener("error", (event) => {
|
|
send({ type: "EVENT_SOURCE_ERROR", error: event })
|
|
})
|
|
|
|
// handle any sse implementation exceptions
|
|
context.eventSource.onerror = () => {
|
|
send({ type: "EVENT_SOURCE_ERROR", error: "sse error" })
|
|
}
|
|
|
|
return () => {
|
|
context.eventSource?.close()
|
|
}
|
|
},
|
|
getBuilds: async (context) => {
|
|
if (context.workspace) {
|
|
// For now, we only retrieve the last month of builds to minimize
|
|
// page bloat. We should add pagination in the future.
|
|
return await API.getWorkspaceBuilds(
|
|
context.workspace.id,
|
|
dayjs().add(-30, "day").toDate(),
|
|
)
|
|
} else {
|
|
throw Error("Cannot get builds without id")
|
|
}
|
|
},
|
|
checkPermissions: async ({ workspace, template }) => {
|
|
if (!workspace) {
|
|
throw new Error("Workspace is not set")
|
|
}
|
|
if (!template) {
|
|
throw new Error("Template is not set")
|
|
}
|
|
return await API.checkAuthorization({
|
|
checks: permissionsToCheck(workspace, template),
|
|
})
|
|
},
|
|
scheduleBannerMachine: workspaceScheduleBannerMachine,
|
|
getSSHPrefix: async () => {
|
|
return API.getDeploymentSSHConfig()
|
|
},
|
|
},
|
|
},
|
|
)
|