diff --git a/engine/sdks/go/api-full/client/client.go b/engine/sdks/go/api-full/client/client.go index ffea4a9578..b830fb0d2c 100644 --- a/engine/sdks/go/api-full/client/client.go +++ b/engine/sdks/go/api-full/client/client.go @@ -281,6 +281,28 @@ func (c *Client) ActorsDelete(ctx context.Context, actorId sdk.RivetId, request return response, nil } +func (c *Client) ActorsKvGet(ctx context.Context, actorId sdk.RivetId, key string) (*sdk.ActorsKvGetResponse, error) { + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + endpointURL := fmt.Sprintf(baseURL+"/"+"actors/%v/kv/keys/%v", actorId, key) + + var response *sdk.ActorsKvGetResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + Headers: c.header, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + func (c *Client) RunnerConfigsList(ctx context.Context, request *sdk.RunnerConfigsListRequest) (*sdk.RunnerConfigsListResponse, error) { baseURL := "" if c.baseURL != "" { diff --git a/engine/sdks/go/api-full/types.go b/engine/sdks/go/api-full/types.go index bec8db4d75..49b205df75 100644 --- a/engine/sdks/go/api-full/types.go +++ b/engine/sdks/go/api-full/types.go @@ -229,6 +229,36 @@ func (a *ActorsGetOrCreateResponse) String() string { return fmt.Sprintf("%#v", a) } +type ActorsKvGetResponse struct { + UpdateTs int64 `json:"update_ts"` + Value string `json:"value"` + + _rawJSON json.RawMessage +} + +func (a *ActorsKvGetResponse) UnmarshalJSON(data []byte) error { + type unmarshaler ActorsKvGetResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = ActorsKvGetResponse(value) + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *ActorsKvGetResponse) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + type ActorsListNamesResponse struct { Names map[string]*ActorName `json:"names,omitempty"` Pagination *Pagination `json:"pagination,omitempty"` diff --git a/engine/sdks/rust/api-full/rust/.openapi-generator/FILES b/engine/sdks/rust/api-full/rust/.openapi-generator/FILES index 70b81c3994..1ce1089616 100644 --- a/engine/sdks/rust/api-full/rust/.openapi-generator/FILES +++ b/engine/sdks/rust/api-full/rust/.openapi-generator/FILES @@ -12,6 +12,8 @@ docs/ActorsDeleteApi.md docs/ActorsGetOrCreateApi.md docs/ActorsGetOrCreateRequest.md docs/ActorsGetOrCreateResponse.md +docs/ActorsKvGetApi.md +docs/ActorsKvGetResponse.md docs/ActorsListApi.md docs/ActorsListNamesApi.md docs/ActorsListNamesResponse.md @@ -70,6 +72,7 @@ git_push.sh src/apis/actors_create_api.rs src/apis/actors_delete_api.rs src/apis/actors_get_or_create_api.rs +src/apis/actors_kv_get_api.rs src/apis/actors_list_api.rs src/apis/actors_list_names_api.rs src/apis/configuration.rs @@ -90,6 +93,7 @@ src/models/actors_create_request.rs src/models/actors_create_response.rs src/models/actors_get_or_create_request.rs src/models/actors_get_or_create_response.rs +src/models/actors_kv_get_response.rs src/models/actors_list_names_response.rs src/models/actors_list_response.rs src/models/crash_policy.rs diff --git a/engine/sdks/rust/api-full/rust/Cargo.toml b/engine/sdks/rust/api-full/rust/Cargo.toml index dbfa5b86b1..089009b450 100644 --- a/engine/sdks/rust/api-full/rust/Cargo.toml +++ b/engine/sdks/rust/api-full/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rivet-api-full" -version = "2.0.23" +version = "2.0.24-rc.1" authors = ["developer@rivet.gg"] description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" license = "Apache-2.0" diff --git a/engine/sdks/rust/api-full/rust/README.md b/engine/sdks/rust/api-full/rust/README.md index e4a1cc9b7d..afa827810b 100644 --- a/engine/sdks/rust/api-full/rust/README.md +++ b/engine/sdks/rust/api-full/rust/README.md @@ -7,8 +7,8 @@ No description provided (generated by Openapi Generator https://github.com/opena This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client. -- API version: 2.0.23 -- Package version: 2.0.23 +- API version: 2.0.24-rc.1 +- Package version: 2.0.24-rc.1 - Generator version: 7.14.0 - Build package: `org.openapitools.codegen.languages.RustClientCodegen` @@ -29,6 +29,7 @@ Class | Method | HTTP request | Description *ActorsCreateApi* | [**actors_create**](docs/ActorsCreateApi.md#actors_create) | **POST** /actors | ## Datacenter Round Trips *ActorsDeleteApi* | [**actors_delete**](docs/ActorsDeleteApi.md#actors_delete) | **DELETE** /actors/{actor_id} | ## Datacenter Round Trips *ActorsGetOrCreateApi* | [**actors_get_or_create**](docs/ActorsGetOrCreateApi.md#actors_get_or_create) | **PUT** /actors | ## Datacenter Round Trips +*ActorsKvGetApi* | [**actors_kv_get**](docs/ActorsKvGetApi.md#actors_kv_get) | **GET** /actors/{actor_id}/kv/keys/{key} | *ActorsListApi* | [**actors_list**](docs/ActorsListApi.md#actors_list) | **GET** /actors | ## Datacenter Round Trips *ActorsListNamesApi* | [**actors_list_names**](docs/ActorsListNamesApi.md#actors_list_names) | **GET** /actors/names | ## Datacenter Round Trips *DatacentersApi* | [**datacenters_list**](docs/DatacentersApi.md#datacenters_list) | **GET** /datacenters | @@ -52,6 +53,7 @@ Class | Method | HTTP request | Description - [ActorsCreateResponse](docs/ActorsCreateResponse.md) - [ActorsGetOrCreateRequest](docs/ActorsGetOrCreateRequest.md) - [ActorsGetOrCreateResponse](docs/ActorsGetOrCreateResponse.md) + - [ActorsKvGetResponse](docs/ActorsKvGetResponse.md) - [ActorsListNamesResponse](docs/ActorsListNamesResponse.md) - [ActorsListResponse](docs/ActorsListResponse.md) - [CrashPolicy](docs/CrashPolicy.md) diff --git a/engine/sdks/rust/api-full/rust/docs/ActorsKvGetApi.md b/engine/sdks/rust/api-full/rust/docs/ActorsKvGetApi.md new file mode 100644 index 0000000000..3b9148f897 --- /dev/null +++ b/engine/sdks/rust/api-full/rust/docs/ActorsKvGetApi.md @@ -0,0 +1,38 @@ +# \ActorsKvGetApi + +All URIs are relative to *http://localhost* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**actors_kv_get**](ActorsKvGetApi.md#actors_kv_get) | **GET** /actors/{actor_id}/kv/keys/{key} | + + + +## actors_kv_get + +> models::ActorsKvGetResponse actors_kv_get(actor_id, key) + + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**actor_id** | **String** | | [required] | +**key** | **String** | | [required] | + +### Return type + +[**models::ActorsKvGetResponse**](ActorsKvGetResponse.md) + +### Authorization + +[bearer_auth](../README.md#bearer_auth) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/engine/sdks/rust/api-full/rust/docs/ActorsKvGetResponse.md b/engine/sdks/rust/api-full/rust/docs/ActorsKvGetResponse.md new file mode 100644 index 0000000000..ae383bba05 --- /dev/null +++ b/engine/sdks/rust/api-full/rust/docs/ActorsKvGetResponse.md @@ -0,0 +1,12 @@ +# ActorsKvGetResponse + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**update_ts** | **i64** | | +**value** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_create_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_create_api.rs index 2b54618f14..1d485614a4 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/actors_create_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_create_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_delete_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_delete_api.rs index 85ae68cb8b..ead0d9162d 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/actors_delete_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_delete_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_get_or_create_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_get_or_create_api.rs index a2024d686b..d73c8754da 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/actors_get_or_create_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_get_or_create_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_kv_get_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_kv_get_api.rs new file mode 100644 index 0000000000..5e0fdf4e0e --- /dev/null +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_kv_get_api.rs @@ -0,0 +1,65 @@ +/* + * rivet-api-public + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.0.24-rc.1 + * Contact: developer@rivet.gg + * Generated by: https://openapi-generator.tech + */ + + +use reqwest; +use serde::{Deserialize, Serialize, de::Error as _}; +use crate::{apis::ResponseContent, models}; +use super::{Error, configuration, ContentType}; + + +/// struct for typed errors of method [`actors_kv_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ActorsKvGetError { + UnknownValue(serde_json::Value), +} + + +pub async fn actors_kv_get(configuration: &configuration::Configuration, actor_id: &str, key: &str) -> Result> { + // add a prefix to parameters to efficiently prevent name collisions + let p_actor_id = actor_id; + let p_key = key; + + let uri_str = format!("{}/actors/{actor_id}/kv/keys/{key}", configuration.base_path, actor_id=crate::apis::urlencode(p_actor_id), key=crate::apis::urlencode(p_key)); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ActorsKvGetResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ActorsKvGetResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_list_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_list_api.rs index 701177009b..5055695b6e 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/actors_list_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_list_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/actors_list_names_api.rs b/engine/sdks/rust/api-full/rust/src/apis/actors_list_names_api.rs index f1c3184398..7e5f74f766 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/actors_list_names_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/actors_list_names_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/configuration.rs b/engine/sdks/rust/api-full/rust/src/apis/configuration.rs index 878f84f725..781f6d999c 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/configuration.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/configuration.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ @@ -40,7 +40,7 @@ impl Default for Configuration { fn default() -> Self { Configuration { base_path: "http://localhost".to_owned(), - user_agent: Some("OpenAPI-Generator/2.0.23/rust".to_owned()), + user_agent: Some("OpenAPI-Generator/2.0.24-rc.1/rust".to_owned()), client: reqwest::Client::new(), basic_auth: None, oauth_access_token: None, diff --git a/engine/sdks/rust/api-full/rust/src/apis/datacenters_api.rs b/engine/sdks/rust/api-full/rust/src/apis/datacenters_api.rs index ec33472c36..34d3f2b39f 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/datacenters_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/datacenters_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/health_api.rs b/engine/sdks/rust/api-full/rust/src/apis/health_api.rs index 33110f94ee..d08f52e506 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/health_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/health_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/mod.rs b/engine/sdks/rust/api-full/rust/src/apis/mod.rs index 4f7b761b75..0d3dd554ae 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/mod.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/mod.rs @@ -114,6 +114,7 @@ impl From<&str> for ContentType { pub mod actors_create_api; pub mod actors_delete_api; pub mod actors_get_or_create_api; +pub mod actors_kv_get_api; pub mod actors_list_api; pub mod actors_list_names_api; pub mod datacenters_api; diff --git a/engine/sdks/rust/api-full/rust/src/apis/namespaces_api.rs b/engine/sdks/rust/api-full/rust/src/apis/namespaces_api.rs index 454f7f1778..42a751d9dd 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/namespaces_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/namespaces_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_delete_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_delete_api.rs index 874ebb7821..d873eb3689 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_delete_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_delete_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_list_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_list_api.rs index 4923f30ea8..2153f7833d 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_list_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_list_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_refresh_metadata_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_refresh_metadata_api.rs index ccbf878efc..9dbed4a82b 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_refresh_metadata_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_refresh_metadata_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_serverless_health_check_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_serverless_health_check_api.rs index fcf39eb3e8..2b25b87833 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_serverless_health_check_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_serverless_health_check_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_upsert_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_upsert_api.rs index 6ade6daea5..d04605edd0 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runner_configs_upsert_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runner_configs_upsert_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/apis/runners_api.rs b/engine/sdks/rust/api-full/rust/src/apis/runners_api.rs index a59b8406bc..ac732db9e0 100644 --- a/engine/sdks/rust/api-full/rust/src/apis/runners_api.rs +++ b/engine/sdks/rust/api-full/rust/src/apis/runners_api.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actor.rs b/engine/sdks/rust/api-full/rust/src/models/actor.rs index dd6ef1ea19..9fd2847053 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actor.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actor.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actor_name.rs b/engine/sdks/rust/api-full/rust/src/models/actor_name.rs index ea503232ba..af844a93d2 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actor_name.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actor_name.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_create_request.rs b/engine/sdks/rust/api-full/rust/src/models/actors_create_request.rs index f08f623832..1166f07a41 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_create_request.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_create_request.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_create_response.rs b/engine/sdks/rust/api-full/rust/src/models/actors_create_response.rs index d057dd12d9..f95763bd06 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_create_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_create_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_request.rs b/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_request.rs index 23d9fe47a0..b85a1b2d83 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_request.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_request.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_response.rs b/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_response.rs index 7683e02205..dbc359c209 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_get_or_create_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_kv_get_response.rs b/engine/sdks/rust/api-full/rust/src/models/actors_kv_get_response.rs new file mode 100644 index 0000000000..67385cd3d6 --- /dev/null +++ b/engine/sdks/rust/api-full/rust/src/models/actors_kv_get_response.rs @@ -0,0 +1,30 @@ +/* + * rivet-api-public + * + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.0.24-rc.1 + * Contact: developer@rivet.gg + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ActorsKvGetResponse { + #[serde(rename = "update_ts")] + pub update_ts: i64, + #[serde(rename = "value")] + pub value: String, +} + +impl ActorsKvGetResponse { + pub fn new(update_ts: i64, value: String) -> ActorsKvGetResponse { + ActorsKvGetResponse { + update_ts, + value, + } + } +} + diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_list_names_response.rs b/engine/sdks/rust/api-full/rust/src/models/actors_list_names_response.rs index 6af808cab6..e687c6c37a 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_list_names_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_list_names_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/actors_list_response.rs b/engine/sdks/rust/api-full/rust/src/models/actors_list_response.rs index 8e2bc81688..c98d1ff199 100644 --- a/engine/sdks/rust/api-full/rust/src/models/actors_list_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/actors_list_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/crash_policy.rs b/engine/sdks/rust/api-full/rust/src/models/crash_policy.rs index 244124b8c3..0be80fb950 100644 --- a/engine/sdks/rust/api-full/rust/src/models/crash_policy.rs +++ b/engine/sdks/rust/api-full/rust/src/models/crash_policy.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/datacenter.rs b/engine/sdks/rust/api-full/rust/src/models/datacenter.rs index 14530ea594..befbd54c65 100644 --- a/engine/sdks/rust/api-full/rust/src/models/datacenter.rs +++ b/engine/sdks/rust/api-full/rust/src/models/datacenter.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/datacenter_health.rs b/engine/sdks/rust/api-full/rust/src/models/datacenter_health.rs index 6d4990a0c1..36dc2faf15 100644 --- a/engine/sdks/rust/api-full/rust/src/models/datacenter_health.rs +++ b/engine/sdks/rust/api-full/rust/src/models/datacenter_health.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/datacenters_list_response.rs b/engine/sdks/rust/api-full/rust/src/models/datacenters_list_response.rs index 200a5e1e0f..ea7421f230 100644 --- a/engine/sdks/rust/api-full/rust/src/models/datacenters_list_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/datacenters_list_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/health_fanout_response.rs b/engine/sdks/rust/api-full/rust/src/models/health_fanout_response.rs index 18fcbedfac..11eae2d447 100644 --- a/engine/sdks/rust/api-full/rust/src/models/health_fanout_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/health_fanout_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/health_response.rs b/engine/sdks/rust/api-full/rust/src/models/health_response.rs index bd14d3eb67..847a163cd4 100644 --- a/engine/sdks/rust/api-full/rust/src/models/health_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/health_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/health_status.rs b/engine/sdks/rust/api-full/rust/src/models/health_status.rs index c5148f86bb..efed70e91d 100644 --- a/engine/sdks/rust/api-full/rust/src/models/health_status.rs +++ b/engine/sdks/rust/api-full/rust/src/models/health_status.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/mod.rs b/engine/sdks/rust/api-full/rust/src/models/mod.rs index 4473ada4e7..8731fb87f5 100644 --- a/engine/sdks/rust/api-full/rust/src/models/mod.rs +++ b/engine/sdks/rust/api-full/rust/src/models/mod.rs @@ -10,6 +10,8 @@ pub mod actors_get_or_create_request; pub use self::actors_get_or_create_request::ActorsGetOrCreateRequest; pub mod actors_get_or_create_response; pub use self::actors_get_or_create_response::ActorsGetOrCreateResponse; +pub mod actors_kv_get_response; +pub use self::actors_kv_get_response::ActorsKvGetResponse; pub mod actors_list_names_response; pub use self::actors_list_names_response::ActorsListNamesResponse; pub mod actors_list_response; diff --git a/engine/sdks/rust/api-full/rust/src/models/namespace.rs b/engine/sdks/rust/api-full/rust/src/models/namespace.rs index 8260333a46..dbc4e63894 100644 --- a/engine/sdks/rust/api-full/rust/src/models/namespace.rs +++ b/engine/sdks/rust/api-full/rust/src/models/namespace.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/namespace_list_response.rs b/engine/sdks/rust/api-full/rust/src/models/namespace_list_response.rs index de0a36ffa9..9dcfa54673 100644 --- a/engine/sdks/rust/api-full/rust/src/models/namespace_list_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/namespace_list_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/namespaces_create_request.rs b/engine/sdks/rust/api-full/rust/src/models/namespaces_create_request.rs index f1ddab1097..3ce64e97cf 100644 --- a/engine/sdks/rust/api-full/rust/src/models/namespaces_create_request.rs +++ b/engine/sdks/rust/api-full/rust/src/models/namespaces_create_request.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/namespaces_create_response.rs b/engine/sdks/rust/api-full/rust/src/models/namespaces_create_response.rs index 629d6fbbc9..efbf03f92f 100644 --- a/engine/sdks/rust/api-full/rust/src/models/namespaces_create_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/namespaces_create_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/pagination.rs b/engine/sdks/rust/api-full/rust/src/models/pagination.rs index 3ae5ecac93..32cb6f9f3d 100644 --- a/engine/sdks/rust/api-full/rust/src/models/pagination.rs +++ b/engine/sdks/rust/api-full/rust/src/models/pagination.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner.rs b/engine/sdks/rust/api-full/rust/src/models/runner.rs index e75c4937a0..bab7d6a9c9 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config.rs index a20984a315..7e4f2e2f37 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind.rs index 28f85bca99..d0409a1440 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of.rs index 0a64003b06..6ca52f6840 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1.rs index 2d180cc918..399e3127f1 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1_serverless.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1_serverless.rs index 4db8e4b72e..9170f7e1ac 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1_serverless.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config_kind_one_of_1_serverless.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_config_variant.rs b/engine/sdks/rust/api-full/rust/src/models/runner_config_variant.rs index 1d866e9f3a..e5fbcc1ac3 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_config_variant.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_config_variant.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response.rs index 3b8be62e06..cf1af497f6 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response_runner_configs_value.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response_runner_configs_value.rs index 354a69686b..c05f52b495 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response_runner_configs_value.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_list_response_runner_configs_value.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_request.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_request.rs index 383ba23bed..05db6de598 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_request.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_request.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response.rs index eeb6137cea..c02df7a3c6 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of.rs index 6d58be2eec..ced8ab0e98 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1.rs index acddf1857f..8beb9e20bd 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1_failure.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1_failure.rs index 459e4b2ff7..fa22b6ca47 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1_failure.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_1_failure.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_success.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_success.rs index 20f2a7cd89..476b9a97e2 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_success.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_health_check_response_one_of_success.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error.rs index 08ca5a2e06..2818c5e37f 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of.rs index 8076e292a0..d6dbaac834 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_1.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_1.rs index c8361cde82..bc3bd6e031 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_1.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_1.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_2.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_2.rs index 83bd0a8df9..b392e1e378 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_2.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_2.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3.rs index 34128fedd2..acbeb57666 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3_non_success_status.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3_non_success_status.rs index 9fee631ea0..1d12e68a8d 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3_non_success_status.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_3_non_success_status.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4.rs index a14080dc8d..8e8a974c37 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4_invalid_response_json.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4_invalid_response_json.rs index 31b557d7b0..ce8293a8c5 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4_invalid_response_json.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_4_invalid_response_json.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5.rs index ef3d28d056..fa31efa306 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5_invalid_response_schema.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5_invalid_response_schema.rs index f4071df26d..80e9143797 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5_invalid_response_schema.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_serverless_metadata_error_one_of_5_invalid_response_schema.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_request_body.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_request_body.rs index fe0cd1197b..2231aa2a0e 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_request_body.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_request_body.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_response.rs b/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_response.rs index 191d6de0f8..17483fd32b 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runner_configs_upsert_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runners_list_names_response.rs b/engine/sdks/rust/api-full/rust/src/models/runners_list_names_response.rs index 0eb303a217..4cc5bd041b 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runners_list_names_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runners_list_names_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/rust/api-full/rust/src/models/runners_list_response.rs b/engine/sdks/rust/api-full/rust/src/models/runners_list_response.rs index 1b5d5284d2..536c2493b2 100644 --- a/engine/sdks/rust/api-full/rust/src/models/runners_list_response.rs +++ b/engine/sdks/rust/api-full/rust/src/models/runners_list_response.rs @@ -3,7 +3,7 @@ * * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * - * The version of the OpenAPI document: 2.0.23 + * The version of the OpenAPI document: 2.0.24-rc.1 * Contact: developer@rivet.gg * Generated by: https://openapi-generator.tech */ diff --git a/engine/sdks/typescript/api-full/src/Client.ts b/engine/sdks/typescript/api-full/src/Client.ts index 5fc7f4c191..b079be13bf 100644 --- a/engine/sdks/typescript/api-full/src/Client.ts +++ b/engine/sdks/typescript/api-full/src/Client.ts @@ -522,6 +522,73 @@ export class RivetClient { } } + /** + * @param {Rivet.RivetId} actorId + * @param {string} key + * @param {RivetClient.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.actorsKvGet("actor_id", "key") + */ + public async actorsKvGet( + actorId: Rivet.RivetId, + key: string, + requestOptions?: RivetClient.RequestOptions, + ): Promise { + const _response = await (this._options.fetcher ?? core.fetcher)({ + url: urlJoin( + (await core.Supplier.get(this._options.baseUrl)) ?? + (await core.Supplier.get(this._options.environment)), + `actors/${encodeURIComponent(serializers.RivetId.jsonOrThrow(actorId))}/kv/keys/${encodeURIComponent(key)}`, + ), + method: "GET", + headers: { + Authorization: await this._getAuthorizationHeader(), + "X-Fern-Language": "JavaScript", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + ...requestOptions?.headers, + }, + contentType: "application/json", + requestType: "json", + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 180000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return serializers.ActorsKvGetResponse.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }); + } + + if (_response.error.reason === "status-code") { + throw new errors.RivetError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.RivetError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.RivetTimeoutError( + "Timeout exceeded when calling GET /actors/{actor_id}/kv/keys/{key}.", + ); + case "unknown": + throw new errors.RivetError({ + message: _response.error.errorMessage, + }); + } + } + /** * @param {Rivet.RunnerConfigsListRequest} request * @param {RivetClient.RequestOptions} requestOptions - Request-specific configuration. diff --git a/engine/sdks/typescript/api-full/src/api/types/ActorsKvGetResponse.ts b/engine/sdks/typescript/api-full/src/api/types/ActorsKvGetResponse.ts new file mode 100644 index 0000000000..547cc1e341 --- /dev/null +++ b/engine/sdks/typescript/api-full/src/api/types/ActorsKvGetResponse.ts @@ -0,0 +1,8 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface ActorsKvGetResponse { + updateTs: number; + value: string; +} diff --git a/engine/sdks/typescript/api-full/src/api/types/index.ts b/engine/sdks/typescript/api-full/src/api/types/index.ts index a665560e68..f57c905265 100644 --- a/engine/sdks/typescript/api-full/src/api/types/index.ts +++ b/engine/sdks/typescript/api-full/src/api/types/index.ts @@ -3,6 +3,7 @@ export * from "./ActorName"; export * from "./ActorsCreateResponse"; export * from "./ActorsDeleteResponse"; export * from "./ActorsGetOrCreateResponse"; +export * from "./ActorsKvGetResponse"; export * from "./ActorsListNamesResponse"; export * from "./ActorsListResponse"; export * from "./CrashPolicy"; diff --git a/engine/sdks/typescript/api-full/src/serialization/types/ActorsKvGetResponse.ts b/engine/sdks/typescript/api-full/src/serialization/types/ActorsKvGetResponse.ts new file mode 100644 index 0000000000..0f96aea33f --- /dev/null +++ b/engine/sdks/typescript/api-full/src/serialization/types/ActorsKvGetResponse.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../index"; +import * as Rivet from "../../api/index"; +import * as core from "../../core"; + +export const ActorsKvGetResponse: core.serialization.ObjectSchema< + serializers.ActorsKvGetResponse.Raw, + Rivet.ActorsKvGetResponse +> = core.serialization.object({ + updateTs: core.serialization.property("update_ts", core.serialization.number()), + value: core.serialization.string(), +}); + +export declare namespace ActorsKvGetResponse { + export interface Raw { + update_ts: number; + value: string; + } +} diff --git a/engine/sdks/typescript/api-full/src/serialization/types/index.ts b/engine/sdks/typescript/api-full/src/serialization/types/index.ts index a665560e68..f57c905265 100644 --- a/engine/sdks/typescript/api-full/src/serialization/types/index.ts +++ b/engine/sdks/typescript/api-full/src/serialization/types/index.ts @@ -3,6 +3,7 @@ export * from "./ActorName"; export * from "./ActorsCreateResponse"; export * from "./ActorsDeleteResponse"; export * from "./ActorsGetOrCreateResponse"; +export * from "./ActorsKvGetResponse"; export * from "./ActorsListNamesResponse"; export * from "./ActorsListResponse"; export * from "./CrashPolicy"; diff --git a/engine/sdks/typescript/runner/src/tunnel.ts b/engine/sdks/typescript/runner/src/tunnel.ts index 1d053d8420..698b1324f8 100644 --- a/engine/sdks/typescript/runner/src/tunnel.ts +++ b/engine/sdks/typescript/runner/src/tunnel.ts @@ -1,11 +1,7 @@ import type * as protocol from "@rivetkit/engine-runner-protocol"; import type { MessageId, RequestId } from "@rivetkit/engine-runner-protocol"; import type { Logger } from "pino"; -import { - parse as uuidparse, - stringify as uuidstringify, - v4 as uuidv4, -} from "uuid"; +import { stringify as uuidstringify, v4 as uuidv4 } from "uuid"; import type { Runner, RunnerActor } from "./mod"; import { stringifyToClientTunnelMessageKind, diff --git a/frontend/package.json b/frontend/package.json index 111eb521dd..bd5374f506 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -67,6 +67,7 @@ "@stepperize/react": "^5.1.8", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/typography": "^0.5.16", + "@tanstack/history": "^1.133.28", "@tanstack/query-core": "^5.87.1", "@tanstack/react-query": "^5.87.1", "@tanstack/react-query-devtools": "^5.87.3", @@ -88,6 +89,7 @@ "@types/node": "^20.19.13", "@types/react": "^19", "@types/react-dom": "^19", + "@types/reconnectingwebsocket": "^1.0.10", "@uiw/codemirror-extensions-basic-setup": "^4.25.1", "@uiw/codemirror-theme-github": "^4.25.1", "@uiw/react-codemirror": "^4.25.1", @@ -96,6 +98,7 @@ "autoprefixer": "^10.4.21", "bcryptjs": "^2.4.3", "canvas-confetti": "^1.9.3", + "cbor-x": "^1.6.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -120,6 +123,7 @@ "react-inspector": "^6.0.2", "react-resizable-panels": "^2.1.9", "recharts": "^2.15.4", + "reconnectingwebsocket": "^1.0.0", "rivetkit": "workspace:*", "shiki": "^3.12.2", "sonner": "^1.7.4", diff --git a/frontend/packages/components/src/actors/actor-build.tsx b/frontend/packages/components/src/actors/actor-build.tsx deleted file mode 100644 index 8416f0dd4e..0000000000 --- a/frontend/packages/components/src/actors/actor-build.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Dd, DiscreteCopyButton, Dl, Dt, Flex } from "@rivet-gg/components"; -import { formatISO } from "date-fns"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { type Actor, type ActorAtom, actorBuildsAtom } from "./actor-context"; -import { ActorTags } from "./actor-tags"; - -const buildIdSelector = (a: Actor) => a.runtime?.build; - -interface ActorBuildProps { - actor: ActorAtom; -} - -export function ActorBuild({ actor }: ActorBuildProps) { - const buildId = useAtomValue(selectAtom(actor, buildIdSelector)); - - const data = useAtomValue( - selectAtom( - actorBuildsAtom, - useCallback( - (builds) => { - return builds.find((build) => build.id === buildId); - }, - [buildId], - ), - ), - ); - - if (!data) { - return null; - } - - return ( -
-
-

Build

-
- -
-
ID
-
- - {data.id} - -
-
Created
-
- - {formatISO(data.createdAt)} - -
-
Tags
-
- - - -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-config-tab.tsx b/frontend/packages/components/src/actors/actor-config-tab.tsx deleted file mode 100644 index 2a05406888..0000000000 --- a/frontend/packages/components/src/actors/actor-config-tab.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Button, DocsSheet, ScrollArea } from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import type { ActorAtom } from "./actor-context"; -import { ActorGeneral } from "./actor-general"; -import { ActorNetwork } from "./actor-network"; -import { ActorRuntime } from "./actor-runtime"; - -interface ActorConfigTabProps { - actor: ActorAtom; -} - -export function ActorConfigTab(props: ActorConfigTabProps) { - return ( - -
- - - -
- - - -
- ); -} diff --git a/frontend/packages/components/src/actors/actor-connections-tab.tsx b/frontend/packages/components/src/actors/actor-connections-tab.tsx deleted file mode 100644 index 42be046a7c..0000000000 --- a/frontend/packages/components/src/actors/actor-connections-tab.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { LiveBadge, ScrollArea } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorObjectInspector } from "./console/actor-inspector"; -import { - useActorConnections, - useActorWorkerStatus, -} from "./worker/actor-worker-context"; - -const selector = (a: Actor) => a.destroyedAt; - -interface ActorConnectionsTabProps { - actor: ActorAtom; -} - -export function ActorConnectionsTab({ actor }: ActorConnectionsTabProps) { - const destroyedAt = useAtomValue(selectAtom(actor, selector)); - const status = useActorWorkerStatus(); - - const connections = useActorConnections(); - - if (destroyedAt) { - return ( -
- Connections Preview is unavailable for inactive Actors. -
- ); - } - - if (status.type === "error") { - return ( -
- Connections Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (status.type !== "ready") { - return ( -
- Loading connections... -
- ); - } - - return ( - -
- -
-
- [c.id, c]))} - expandPaths={["$"]} - /> -
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-context.tsx b/frontend/packages/components/src/actors/actor-context.tsx deleted file mode 100644 index 7aab38c8fa..0000000000 --- a/frontend/packages/components/src/actors/actor-context.tsx +++ /dev/null @@ -1,437 +0,0 @@ -import type { Rivet } from "@rivet-gg/api"; -import { isAfter, isBefore } from "date-fns"; -import { type Atom, atom } from "jotai"; -import { atomFamily, splitAtom } from "jotai/utils"; -import { toRecord } from "../lib/utils"; -import { FilterOp, type FilterValue } from "../ui/filters"; -import { ACTOR_FRAMEWORK_TAG_VALUE } from "./actor-tags"; - -export enum ActorFeature { - Logs = "logs", - Config = "config", - Connections = "connections", - State = "state", - Console = "console", - Runtime = "runtime", - Metrics = "metrics", - InspectReconnectNotification = "inspect_reconnect_notification", -} - -export type Actor = Omit< - Rivet.actor.Actor, - "createdAt" | "runtime" | "lifecycle" | "network" | "resources" -> & { - status: "unknown" | "starting" | "running" | "stopped" | "crashed"; - - lifecycle?: Rivet.actor.Lifecycle; - endpoint?: string; - logs: LogsAtom; - metrics: MetricsAtom; - network?: Rivet.actor.Network | null; - resources?: Rivet.actor.Resources | null; - runtime?: Rivet.actor.Runtime | null; - destroy?: DestroyActorAtom; - destroyTs?: Date; - createdAt?: Date; - features?: ActorFeature[]; -}; - -export type Logs = { - id: string; - level: "error" | "info"; - timestamp: Date; - line: string; - message: string; - properties: Record; -}[]; - -export type Metrics = Record; - -export type Build = Rivet.actor.Build; -export type DestroyActor = { - isDestroying: boolean; - destroy: () => Promise; -}; - -export type ActorAtom = Atom; -export type LogsAtom = Atom<{ - logs: Logs; - // query status - status: string; -}>; -export type MetricsAtom = Atom<{ - metrics: Metrics; - updatedAt: number; - // query status - status: string; -}>; -export type BuildAtom = Atom; -export type DestroyActorAtom = Atom; - -export type CreateActor = { - create: (values: { - endpoint: string; - id: string; - tags: Record; - region?: string; - params?: Record; - }) => Promise; - isCreating: boolean; - endpoint: string | null; -}; - -export type Region = Rivet.actor.Region; - -// global atoms -export const currentActorIdAtom = atom(undefined); - -export const currentActorQueryAtom = atom<{ - isLoading: boolean; - error: string | null; -}>({ - isLoading: false, - error: null, -}); -export const actorsQueryAtom = atom<{ - isLoading: boolean; - error: string | null; -}>({ - isLoading: false, - error: null, -}); -export const actorsAtom = atom([]); -export const actorFiltersAtom = atom<{ - tags: FilterValue; - region: FilterValue; - createdAt: FilterValue; - destroyedAt: FilterValue; - status: FilterValue; - devMode: FilterValue; -}>({ - tags: undefined, - region: undefined, - createdAt: undefined, - destroyedAt: undefined, - status: undefined, - devMode: undefined, -}); -export const actorsPaginationAtom = atom({ - hasNextPage: false, - isFetchingNextPage: false, - fetchNextPage: () => {}, -}); - -export const actorRegionsAtom = atom([ - { - id: "default", - name: "Default", - }, -]); - -export const actorBuildsAtom = atom([]); - -export const actorEnvironmentAtom = atom<{ - projectNameId: string; - environmentNameId: string; -} | null>(null); - -export const actorMetricsTimeWindowAtom = atom(15 * 60 * 1000); // Default to 15 minutes - -export const actorsInternalFilterAtom = atom<{ - fn: (actor: Actor) => boolean; -}>(); - -// derived atoms - -export const currentActorRegionAtom = atom((get) => { - const actorAtom = get(currentActorAtom); - if (!actorAtom) { - return undefined; - } - const regions = get(actorRegionsAtom); - const actor = get(actorAtom); - return regions.find((region) => region.id === actor.region); -}); -export const filteredActorsAtom = atom((get) => { - const filters = get(actorFiltersAtom); - const actors = get(actorsAtom); - - const isActorInternal = get(actorsInternalFilterAtom)?.fn; - - return actors.filter((actor) => { - const satisfiesFilters = Object.entries(filters).every( - ([key, filter]) => { - if (filter === undefined) { - return true; - } - if (key === "tags") { - const filterTags = filter.value.map((tag) => - tag.split("="), - ); - const tags = toRecord(actor.tags); - - if (filter.operator === FilterOp.NOT_EQUAL) { - return Object.entries(tags).every( - ([tagKey, tagValue]) => { - return filterTags.every( - ([filterKey, filterValue]) => { - if (filterKey === tagKey) { - if (filterValue === "*") { - return false; - } - return tagValue !== filterValue; - } - return true; - }, - ); - }, - ); - } - - return Object.entries(tags).some(([tagKey, tagValue]) => { - return filterTags.some(([filterKey, filterValue]) => { - if (filterKey === tagKey) { - if (filterValue === "*") { - return true; - } - return tagValue === filterValue; - } - return false; - }); - }); - } - - if (key === "region") { - if (filter.operator === FilterOp.NOT_EQUAL) { - return !filter.value.includes(actor.region); - } - - return filter.value.includes(actor.region); - } - - if (key === "createdAt") { - if (actor.createdAt === undefined) { - return false; - } - const createdAt = new Date(actor.createdAt); - - if (filter.operator === FilterOp.AFTER) { - return isAfter(createdAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BEFORE) { - return isBefore(createdAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BETWEEN) { - return ( - isAfter(createdAt, +filter.value[0]) && - isBefore(createdAt, +filter.value[1]) - ); - } - return false; - } - - if (key === "destroyedAt") { - if (actor.destroyTs === undefined) { - return false; - } - const destroyedAt = new Date(actor.destroyTs); - - if (filter.operator === FilterOp.AFTER) { - return isAfter(destroyedAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BEFORE) { - return isBefore(destroyedAt, +filter.value[0]); - } - if (filter.operator === FilterOp.BETWEEN) { - return ( - isAfter(destroyedAt, +filter.value[0]) && - isBefore(destroyedAt, +filter.value[1]) - ); - } - return false; - } - - if (key === "status") { - if (filter.operator === FilterOp.NOT_EQUAL) { - return !filter.value.includes(actor.status); - } - - return filter.value.includes(actor.status); - } - - return true; - }, - ); - - const isInternal = - toRecord(actor.tags).owner === "rivet" || - (isActorInternal?.(actor) ?? false); - - return ( - satisfiesFilters && ((isInternal && filters.devMode) || !isInternal) - ); - }); -}); -export const actorsAtomsAtom = splitAtom( - filteredActorsAtom, - (actor) => actor.id, -); -export const actorsCountAtom = atom((get) => get(actorsAtom).length); -export const filteredActorsCountAtom = atom( - (get) => get(filteredActorsAtom).length, -); - -export const currentActorAtom = atom((get) => { - const actorId = get(currentActorIdAtom); - return get(actorsAtomsAtom).find((actor) => get(actor).id === actorId); -}); - -export const isCurrentActorAtom = atomFamily((actor: ActorAtom) => - atom((get) => { - const actorId = get(currentActorIdAtom); - return get(actor).id === actorId; - }), -); - -export const actorFiltersCountAtom = atom((get) => { - const filters = get(actorFiltersAtom); - return Object.values(filters).filter((value) => value !== undefined).length; -}); - -// tags created by the user, not from the server -export const actorCustomTagValues = atom([]); -export const actorCustomTagKeys = atom([]); - -const actorCustomTagsAtom = atom<{ keys: string[]; values: string[] }>( - (get) => { - const keys = get(actorCustomTagKeys); - const values = get(actorCustomTagValues); - - return { keys, values }; - }, - // @ts-expect-error - (get, set, value: { key: string; value: string }) => { - set(actorCustomTagKeys, (keys) => { - const newKeys = [...keys]; - const index = newKeys.indexOf(value.key); - if (index === -1) { - newKeys.push(value.key); - } - return newKeys; - }); - set(actorCustomTagValues, (values) => { - const newValues = [...values]; - const index = newValues.indexOf(value.value); - if (index === -1) { - newValues.push(value.value); - } - return newValues; - }); - }, -); - -export const createActorAtom = atom({ - endpoint: null, - isCreating: false, - create: async () => {}, -}); - -export const actorManagerEndpointAtom = atom((get) => { - return get(createActorAtom)?.endpoint ?? null; -}); - -export const actorTagsAtom = atom((get) => { - const actorTags = get(actorsAtom).flatMap((actor) => - Object.entries(toRecord(actor.tags)).map(([key, value]) => ({ - key, - value: value as string, - })), - ); - - const keys = new Set(); - const values = new Set(); - - for (const { key, value } of actorTags) { - keys.add(key); - values.add(value); - } - - const customTags = get(actorCustomTagsAtom); - - for (const key of customTags.keys) { - keys.add(key); - } - - for (const value of customTags.values) { - values.add(value); - } - - const allTags = []; - - for (const key of keys) { - for (const value of values) { - allTags.push({ key, value }); - } - } - - return allTags; -}); - -export const actorTagValuesAtom = atom((get) => { - const tags = get(actorTagsAtom); - const values = new Set(); - for (const tag of tags) { - values.add(tag.value); - } - return [...values]; -}); - -export const actorTagKeysAtom = atom((get) => { - const tags = get(actorTagsAtom); - const keys = new Set(); - for (const tag of tags) { - keys.add(tag.key); - } - return [...keys]; -}); - -export const actorBuildsCountAtom = atom((get) => { - return get(actorBuildsAtom).length; -}); - -const commonActorFeatures = [ - ActorFeature.Logs, - ActorFeature.Config, - ActorFeature.Runtime, - // ActorFeature.Metrics, - // ActorFeature.InspectReconnectNotification, -]; - -export const currentActorFeaturesAtom = atom((get) => { - const atom = get(currentActorAtom); - if (!atom) { - return []; - } - - const actor = get(atom); - - // actors from hub - if (!actor.features) { - const tags = toRecord(actor.tags); - if (tags.framework === ACTOR_FRAMEWORK_TAG_VALUE) { - if (tags.name === "manager") { - return commonActorFeatures; - } - return [ - ...commonActorFeatures, - // ActorFeature.Connections, - // ActorFeature.State, - // ActorFeature.Console, - // ActorFeature.InspectReconnectNotification, - ]; - } - return [...commonActorFeatures, ActorFeature.Metrics]; - } - - return actor.features; -}); diff --git a/frontend/packages/components/src/actors/actor-cpu-stats.tsx b/frontend/packages/components/src/actors/actor-cpu-stats.tsx deleted file mode 100644 index 2cf694f339..0000000000 --- a/frontend/packages/components/src/actors/actor-cpu-stats.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { format } from "date-fns"; -import { useId } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { timing } from "../lib/timing"; -import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "../ui/chart"; - -interface ActorCpuStatsProps { - interval?: number; - cpu: number[]; - metricsAt: number; - syncId?: string; - isRunning?: boolean; -} - -const chartConfig = { - value: { - color: "hsl(var(--chart-1))", - label: "CPU Usage", - }, -} satisfies ChartConfig; - -export function ActorCpuStats({ - interval = 15, - cpu, - metricsAt, - syncId, - isRunning = true, -}: ActorCpuStatsProps) { - // Filter out trailing zeros in the last 15 seconds only if actor is still running - let filteredCpu = [...cpu]; - if (isRunning) { - const secondsToCheck = 15; - const pointsToCheck = Math.ceil(secondsToCheck / interval); - - // Find the last non-zero value and cut off any zeros after it - for ( - let i = filteredCpu.length - 1; - i >= Math.max(0, filteredCpu.length - pointsToCheck); - i-- - ) { - if (filteredCpu[i] === 0) { - filteredCpu = filteredCpu.slice(0, i); - } else { - break; - } - } - } - - const data = filteredCpu.map((value, i) => { - let cpuPercent = 0; - - // Calculate CPU percentage using delta time between ticks - if (i > 0) { - const currentCpuTime = value; - const previousCpuTime = filteredCpu[i - 1]; - const deltaTime = interval; // seconds between measurements - - // CPU percentage = (cpu_time_delta / time_delta) * 100 - // This gives us the percentage of CPU time used in the interval - if (currentCpuTime >= previousCpuTime) { - cpuPercent = Math.min( - ((currentCpuTime - previousCpuTime) / deltaTime) * 100, - 100, - ); - } - } - - return { - x: `${(filteredCpu.length - i) * -interval}`, - value: cpuPercent / 100, // Convert to 0-1 range for chart - config: { - label: new Date( - metricsAt - - (filteredCpu.length - i) * timing.seconds(interval), - ), - }, - }; - }); - - const id = useId(); - - const fillId = `fill-${id}`; - return ( - - - - - `${value * 100}%`} - /> - { - return format(label, "HH:mm:ss"); - }} - valueFormatter={(value) => { - if (typeof value !== "number") { - return "n/a"; - } - return `${(value * 100).toFixed(2)}%`; - }} - /> - } - /> - - - - - - - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-details-settings-button.tsx b/frontend/packages/components/src/actors/actor-details-settings-button.tsx deleted file mode 100644 index 9e4684bd40..0000000000 --- a/frontend/packages/components/src/actors/actor-details-settings-button.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, - WithTooltip, -} from "@rivet-gg/components"; -import { Icon, faCog } from "@rivet-gg/icons"; -import { useActorDetailsSettings } from "./actor-details-settings"; - -export function ActorDetailsSettingsButton() { - const [settings, setSettings] = useActorDetailsSettings(); - - return ( - - - - - } - content="Settings" - /> - - { - setSettings((old) => ({ - ...old, - showTimestamps: value, - })); - }} - > - Show timestamps - - { - setSettings((old) => ({ - ...old, - autoFollowLogs: value, - })); - }} - > - Auto follow logs when scrolled to bottom - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-details-settings.tsx b/frontend/packages/components/src/actors/actor-details-settings.tsx deleted file mode 100644 index 622ce862e4..0000000000 --- a/frontend/packages/components/src/actors/actor-details-settings.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - type Dispatch, - type ReactNode, - type SetStateAction, - createContext, - useContext, -} from "react"; -import { useLocalStorage } from "usehooks-ts"; - -export interface Settings { - showTimestamps: boolean; - autoFollowLogs: boolean; -} - -export const ActorDetailsSettingsContext = createContext< - [Settings, Dispatch>, unknown] ->([{ showTimestamps: false, autoFollowLogs: true }, () => {}, {}]); - -export const useActorDetailsSettings = () => { - const value = useContext(ActorDetailsSettingsContext); - return value; -}; - -interface ActorDetailsSettingsProviderProps { - children: ReactNode; -} - -export const ActorDetailsSettingsProvider = ({ - children, -}: ActorDetailsSettingsProviderProps) => { - const localStorage = useLocalStorage("actor-details-settings", { - showTimestamps: false, - autoFollowLogs: true, - }); - - return ( - - {children} - - ); -}; diff --git a/frontend/packages/components/src/actors/actor-download-logs-button.tsx b/frontend/packages/components/src/actors/actor-download-logs-button.tsx deleted file mode 100644 index a261529c32..0000000000 --- a/frontend/packages/components/src/actors/actor-download-logs-button.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Button, WithTooltip } from "@rivet-gg/components"; -import { Icon, faSave } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import type { ActorAtom } from "./actor-context"; -import type { LogsTypeFilter } from "./actor-logs"; - -interface ActorDownloadLogsButtonProps { - actor: ActorAtom; - typeFilter?: LogsTypeFilter; - filter?: string; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExporting?: boolean; -} - -export function ActorDownloadLogsButton({ - actor, - typeFilter, - filter, - onExportLogs, - isExporting = false, -}: ActorDownloadLogsButtonProps) { - const actorData = useAtomValue(actor); - - const handleDownload = async () => { - if (!onExportLogs) { - console.warn("No export handler provided"); - return; - } - - try { - await onExportLogs(actorData.id, typeFilter, filter); - } catch (error) { - console.error("Failed to export logs:", error); - } - }; - - return ( - - - - } - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-editable-state.tsx b/frontend/packages/components/src/actors/actor-editable-state.tsx deleted file mode 100644 index e54e474548..0000000000 --- a/frontend/packages/components/src/actors/actor-editable-state.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { Badge, Button, WithTooltip } from "@rivet-gg/components"; -import { - type CodeMirrorRef, - EditorView, - JsonCode, -} from "@rivet-gg/components/code-mirror"; -import { Icon, faRotateLeft, faSave } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import { useMemo, useRef, useState } from "react"; -import { ActorStateChangeIndicator } from "./actor-state-change-indicator"; -import type { ContainerState } from "./worker/actor-worker-container"; -import { useActorWorker } from "./worker/actor-worker-context"; - -const isValidJson = (json: string | null): json is string => { - if (!json) return false; - try { - JSON.parse(json); - return true; - } catch { - return false; - } -}; - -interface ActorEditableStateProps { - state: ContainerState["state"]; -} - -export function ActorEditableState({ state }: ActorEditableStateProps) { - const container = useActorWorker(); - const [isEditing, setIsEditing] = useState(false); - const [value, setValue] = useState(null); - - const ref = useRef(null); - - const formatted = useMemo(() => { - return JSON.stringify(state.value || "{}", null, 2); - }, [state.value]); - - const isValid = isValidJson(value) ? JSON.parse(value) : false; - - return ( - <> -
-
- -
-
- - {isEditing ? ( - - - Modified - - - } - content="State has been modified and not saved." - /> - ) : null} - - { - container.setState(value || ""); - setIsEditing(false); - setValue(null); - }} - > - - - } - /> - { - setValue(null); - setIsEditing(false); - }} - > - - - } - /> -
-
-
- { - setValue(value); - setIsEditing(true); - }} - /> -
- - ); -} diff --git a/frontend/packages/components/src/actors/actor-general.tsx b/frontend/packages/components/src/actors/actor-general.tsx deleted file mode 100644 index b9269fb39e..0000000000 --- a/frontend/packages/components/src/actors/actor-general.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Dd, DiscreteCopyButton, Dl, Dt, Flex, cn } from "@rivet-gg/components"; -import { formatISO } from "date-fns"; -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorRegion } from "./actor-region"; -import { ActorTags } from "./actor-tags"; - -const selector = (a: Actor) => ({ - id: a.id, - tags: a.tags, - createdAt: a.createdAt, - destroyedAt: a.destroyedAt, - region: a.region, -}); - -export interface ActorGeneralProps { - actor: ActorAtom; -} - -export function ActorGeneral({ actor }: ActorGeneralProps) { - const { id, tags, createdAt, destroyedAt, region } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - return ( -
-

General

- -
-
Region
-
- -
-
ID
-
- - {id} - -
-
Tags
-
- - - -
-
Created
-
- - {createdAt ? formatISO(createdAt) : "n/a"} - -
-
Destroyed
-
- - {destroyedAt ? formatISO(destroyedAt) : "n/a"} - -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-logs-tab.tsx b/frontend/packages/components/src/actors/actor-logs-tab.tsx deleted file mode 100644 index 7925e94cc0..0000000000 --- a/frontend/packages/components/src/actors/actor-logs-tab.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { LogsView, ToggleGroup, ToggleGroupItem } from "@rivet-gg/components"; -import { startTransition, useState } from "react"; -import type { ActorAtom } from "./actor-context"; -import { ActorDetailsSettingsButton } from "./actor-details-settings-button"; -import { ActorDownloadLogsButton } from "./actor-download-logs-button"; -import { ActorLogs, type LogsTypeFilter } from "./actor-logs"; - -interface ActorLogsTabProps { - actor: ActorAtom; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExporting?: boolean; -} - -export function ActorLogsTab({ - actor, - onExportLogs, - isExporting, -}: ActorLogsTabProps) { - const [search, setSearch] = useState(""); - const [logsFilter, setLogsFilter] = useState("all"); - - return ( -
-
-
-
- - startTransition(() => setSearch(e.target.value)) - } - /> -
- { - if (!value) { - setLogsFilter("all"); - } else { - setLogsFilter(value as LogsTypeFilter); - } - }} - className="gap-0 text-xs p-2 border-r" - > - - all - - - output - - - errors - - - - -
-
-
- -
-
- ); -} - -ActorLogsTab.Skeleton = () => { - return ( -
- -
- ); -}; diff --git a/frontend/packages/components/src/actors/actor-logs.tsx b/frontend/packages/components/src/actors/actor-logs.tsx deleted file mode 100644 index 11427cab70..0000000000 --- a/frontend/packages/components/src/actors/actor-logs.tsx +++ /dev/null @@ -1,219 +0,0 @@ -import { ShimmerLine, VirtualScrollArea } from "@rivet-gg/components"; -import type { Virtualizer } from "@tanstack/react-virtual"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { memo, useCallback, useEffect, useRef } from "react"; -import { useResizeObserver } from "usehooks-ts"; -import type { Actor, ActorAtom, Logs } from "./actor-context"; -import { useActorDetailsSettings } from "./actor-details-settings"; -import { ActorConsoleMessage } from "./console/actor-console-message"; - -export type LogsTypeFilter = "all" | "output" | "errors"; - -const selector = (a: Actor) => a.logs; - -interface ActorLogsProps { - actor: ActorAtom; - typeFilter?: LogsTypeFilter; - filter?: string; -} - -export const ActorLogs = memo( - ({ typeFilter, actor, filter }: ActorLogsProps) => { - const [settings] = useActorDetailsSettings(); - const follow = useRef(true); - const shouldFollow = () => settings.autoFollowLogs && follow.current; - - const viewport = useRef(null); - const virtualizer = useRef>(null); - // Detect if the container has resized (i.e, console was opened) - useResizeObserver({ - ref: viewport, - onResize: () => { - if (shouldFollow()) { - // https://github.com/TanStack/virtual/issues/537 - requestAnimationFrame(() => { - virtualizer.current?.scrollToIndex(combined.length, { - align: "end", - }); - }); - } - }, - }); - - const logsAtom = useAtomValue(selectAtom(actor, selector)); - - const { logs, status } = useAtomValue(logsAtom); - - const combined = filterLogs({ - typeFilter: typeFilter ?? "all", - filter: filter ?? "", - logs, - }); - - // Scroll to the bottom when new logs are added - // biome-ignore lint/correctness/useExhaustiveDependencies: run this effect only when the length of the logs changes - useEffect(() => { - if (!shouldFollow()) { - return () => {}; - } - // https://github.com/TanStack/virtual/issues/537 - const rafId = requestAnimationFrame(() => { - virtualizer.current?.scrollToIndex( - virtualizer.current.options.count - 1, - { - align: "end", - }, - ); - }); - - return () => { - cancelAnimationFrame(rafId); - }; - }, [combined.length]); - - // Detect if the user has scrolled all the way to the bottom - const handleChange = useCallback( - (instance: Virtualizer, sync: boolean) => { - if (sync) { - return; - } - - follow.current = - !instance.isScrolling && - instance.range?.endIndex === instance.options.count - 1; - }, - [], - ); - - // if (isStdOutLoading || isStdErrLoading) { - // return ( - //
- // - // Loading logs... - // - //
- // ); - // } - - // const status = getActorStatus({ createdAt, startedAt, destroyedAt }); - - if (status === "starting" && combined.length === 0) { - return ( -
- - [SYSTEM]: Actor is starting... - -
- ); - } - - if (status === "pending") { - return ( - <> - -
- - Loading logs... - -
- - ); - } - - if (combined.length === 0) { - // if (!isStdOutSuccess || !isStdErrSuccess) { - // return ( - //
- // - // [SYSTEM]: Couldn't find the logs. Please try again - // later. - // - //
- // ); - // } - return ( -
- - [SYSTEM]: No logs found. Logs are retained for 3 days. - -
- ); - } - - return ( - <> - - ({ - ...combined[index], - children: - combined[index].message || combined[index].line, - variant: combined[index].level, - timestamp: settings.showTimestamps - ? combined[index].timestamp - : undefined, - })} - onChange={handleChange} - count={combined.length} - estimateSize={() => 26} - row={ActorConsoleMessage} - /> - - ); - }, -); - -interface ScrollerProps { - virtualizer: React.MutableRefObject | null>; -} - -function Scroller({ virtualizer }: ScrollerProps) { - // biome-ignore lint/correctness/useExhaustiveDependencies: scroll on mount, no need to run this effect again - useEffect(() => { - // https://github.com/TanStack/virtual/issues/537 - virtualizer.current?.scrollToIndex( - virtualizer.current.options.count - 1, - { - align: "end", - }, - ); - }, []); - - return null; -} - -export function filterLogs({ - typeFilter, - filter, - logs, -}: { typeFilter: LogsTypeFilter; filter: string; logs: Logs }) { - const output = logs?.filter((log) => { - if (typeFilter === "errors") { - return log.level === "error"; - } - if (typeFilter === "output") { - return log.level !== "error"; - } - return true; - }); - - // Search - const filtered = - filter && filter.trim() !== "" - ? output.filter((log) => log.message.includes(filter)) - : output; - - const sorted = filtered.toSorted( - (a, b) => - new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf(), - ); - - return sorted; -} diff --git a/frontend/packages/components/src/actors/actor-memory-stats.tsx b/frontend/packages/components/src/actors/actor-memory-stats.tsx deleted file mode 100644 index f2af24d75d..0000000000 --- a/frontend/packages/components/src/actors/actor-memory-stats.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { format } from "date-fns"; -import { filesize } from "filesize"; -import { useId } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { timing } from "../lib/timing"; -import { - type ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from "../ui/chart"; - -interface ActorMemoryStatsProps { - metricsAt: number; - memory: number[]; - allocatedMemory?: number; - syncId?: string; - interval?: number; - isRunning?: boolean; -} - -const chartConfig = { - value: { - color: "hsl(var(--chart-1))", - label: "Memory Usage", - }, -} satisfies ChartConfig; - -export function ActorMemoryStats({ - interval = 15, - memory, - allocatedMemory, - metricsAt, - syncId, - isRunning = true, -}: ActorMemoryStatsProps) { - // Filter out trailing zeros in the last 15 seconds only if actor is still running - let filteredMemory = [...memory]; - if (isRunning) { - const secondsToCheck = 15; - const pointsToCheck = Math.ceil(secondsToCheck / interval); - - // Find the last non-zero value and cut off any zeros after it - for ( - let i = filteredMemory.length - 1; - i >= Math.max(0, filteredMemory.length - pointsToCheck); - i-- - ) { - if (filteredMemory[i] === 0) { - filteredMemory = filteredMemory.slice(0, i); - } else { - break; - } - } - } - - const data = filteredMemory.map((value, i) => ({ - x: `${(filteredMemory.length - i) * -interval}`, - value, - config: { - label: new Date( - metricsAt - - (filteredMemory.length - i) * timing.seconds(interval), - ), - }, - })); - - const max = allocatedMemory || Math.max(...filteredMemory); - - const id = useId(); - - const fillId = `fill-${id}`; - return ( - - - - - - `${Math.ceil((value / max) * 100)}%` - } - /> - { - return format(label, "HH:mm:ss"); - }} - valueFormatter={(value) => { - if (typeof value !== "number") { - return "n/a"; - } - return `${filesize(value)} (${Math.round((value / max) * 100).toFixed(2)}%)`; - }} - /> - } - /> - - - - - - - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-metrics-tab.tsx b/frontend/packages/components/src/actors/actor-metrics-tab.tsx deleted file mode 100644 index edc11a6618..0000000000 --- a/frontend/packages/components/src/actors/actor-metrics-tab.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, ScrollArea } from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import { ActorMetrics } from "./actor-metrics"; -import type { ActorAtom } from "./actor-context"; - -interface ActorMetricsTabProps { - actor: ActorAtom; -} - -export function ActorMetricsTab(props: ActorMetricsTabProps) { - return ( - -
- -
- -
- ); -} \ No newline at end of file diff --git a/frontend/packages/components/src/actors/actor-metrics.tsx b/frontend/packages/components/src/actors/actor-metrics.tsx deleted file mode 100644 index 947d1166dc..0000000000 --- a/frontend/packages/components/src/actors/actor-metrics.tsx +++ /dev/null @@ -1,704 +0,0 @@ -import { useAtomValue, useSetAtom } from "jotai"; -import { selectAtom } from "jotai/utils"; -import equal from "fast-deep-equal"; -import { useState, useMemo } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorCpuStats } from "./actor-cpu-stats"; -import { ActorMemoryStats } from "./actor-memory-stats"; -import { Dd, Dl, Dt } from "../ui/typography"; -import { Button } from "../ui/button"; -import { Flex } from "../ui/flex"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"; -import { actorMetricsTimeWindowAtom, actorEnvironmentAtom } from "./actor-context"; -import { useQuery } from "@tanstack/react-query"; -import { actorMetricsQueryOptions } from "@/domains/project/queries/actors/query-options"; - -const selector = (a: Actor) => ({ - metrics: a.metrics, - status: a.status, - resources: a.resources, - id: a.id, -}); - -const timeWindowOptions = [ - { label: "5 minutes", value: "5m", milliseconds: 5 * 60 * 1000 }, - { label: "15 minutes", value: "15m", milliseconds: 15 * 60 * 1000 }, - { label: "30 minutes", value: "30m", milliseconds: 30 * 60 * 1000 }, - { label: "1 hour", value: "1h", milliseconds: 60 * 60 * 1000 }, - { label: "3 hours", value: "3h", milliseconds: 3 * 60 * 60 * 1000 }, - { label: "6 hours", value: "6h", milliseconds: 6 * 60 * 60 * 1000 }, - { label: "12 hours", value: "12h", milliseconds: 12 * 60 * 60 * 1000 }, - { label: "24 hours", value: "24h", milliseconds: 24 * 60 * 60 * 1000 }, - { label: "2 days", value: "2d", milliseconds: 2 * 24 * 60 * 60 * 1000 }, -]; - -export interface ActorMetricsProps { - actor: ActorAtom; -} - -export function ActorMetrics({ actor }: ActorMetricsProps) { - const { metrics, status, resources, id } = useAtomValue( - selectAtom(actor, selector, equal), - ); - const defaultMetricsData = useAtomValue(metrics); - const [showAdvanced, setShowAdvanced] = useState(false); - - const timeWindowMs = useAtomValue(actorMetricsTimeWindowAtom); - const setTimeWindowMs = useSetAtom(actorMetricsTimeWindowAtom); - const environment = useAtomValue(actorEnvironmentAtom); - - const currentTimeWindow = timeWindowOptions.find(option => option.milliseconds === timeWindowMs) || timeWindowOptions[1]; - const [timeWindow, setTimeWindow] = useState(currentTimeWindow.value); - - const isActorRunning = status === "running"; - - // Create a query for time window-specific metrics - const { data: customMetricsData, status: customMetricsStatus } = useQuery({ - ...actorMetricsQueryOptions( - { - projectNameId: environment?.projectNameId || "", - environmentNameId: environment?.environmentNameId || "", - actorId: id, - timeWindowMs: timeWindowMs, - }, - { refetchInterval: 5000 } - ), - enabled: !!environment && !!id, - }); - - // Use custom metrics if available, otherwise fall back to default - const metricsData = customMetricsData ? { - metrics: customMetricsData.metrics, - rawData: customMetricsData.rawData, - interval: customMetricsData.interval, - status: customMetricsStatus, - updatedAt: Date.now(), - } : defaultMetricsData; - - const handleTimeWindowChange = (value: string) => { - setTimeWindow(value); - const selectedOption = timeWindowOptions.find(option => option.value === value); - if (selectedOption) { - setTimeWindowMs(selectedOption.milliseconds); - } - }; - - const formatBytes = (bytes: number | null | undefined) => { - if (!isActorRunning || bytes === null || bytes === undefined) - return "n/a"; - const mb = bytes / 1024 / 1024; - if (mb < 1024) { - return `${mb.toFixed(1)} MB`; - } - return `${(mb / 1024).toFixed(1)} GB`; - }; - - const formatCpuUsage = (cpu: number | null | undefined) => { - if (!isActorRunning || cpu === null || cpu === undefined) return "n/a"; - return `${(cpu * 100).toFixed(2)}%`; - }; - - const formatNumber = (value: number | null | undefined) => { - if (!isActorRunning || value === null || value === undefined) - return "n/a"; - return value.toLocaleString(); - }; - - const formatTimestamp = (timestamp: number | null | undefined) => { - if (!isActorRunning || timestamp === null || timestamp === undefined) - return "n/a"; - return new Date(timestamp * 1000).toLocaleString(); - }; - - // Calculate CPU percentage using time series data points - const cpuPercentage = useMemo(() => { - if (!isActorRunning) { - return "Stopped"; - } - - const data = metricsData; - if (!data || !data.rawData || !data.interval) { - return "n/a"; - } - - const cpuValues = data.rawData.cpu_usage_seconds_total; - if (!cpuValues || cpuValues.length < 2) { - return "n/a"; - } - - // Find the last valid CPU rate from the most recent data points - let cpuRate = 0; - for (let i = cpuValues.length - 1; i > 0; i--) { - const currentCpu = cpuValues[i]; - const previousCpu = cpuValues[i - 1]; - - if ( - currentCpu !== 0 && - previousCpu !== 0 && - currentCpu >= previousCpu - ) { - const cpuDelta = currentCpu - previousCpu; - const timeDelta = data.interval / 1000; // Convert ms to seconds - - // Rate calculation: CPU seconds used per second of real time - // This gives the fraction of available CPU used (0-1) - cpuRate = (cpuDelta / timeDelta) * 100; - break; - } - } - - return `${Math.min(cpuRate, 100).toFixed(2)}%`; - }, [metricsData, isActorRunning]); - - const calculateMemoryPercentage = ( - usage: number | null | undefined, - ) => { - if ( - !isActorRunning || - usage === null || - usage === undefined || - !resources || - !resources.memory || - resources.memory === 0 - ) { - return null; - } - // Convert usage from bytes to MB and compare with resources.memory (which is in MB) - const usageMB = usage / (1024 * 1024); - return (usageMB / resources.memory) * 100; - }; - - const isLoading = metricsData.status === "pending"; - const hasError = metricsData.status === "error"; - const data = metricsData.metrics || {}; - - if (isLoading) { - return ( -
-

Metrics

-
Loading...
-
- ); - } - - if (hasError) { - return ( -
-

Metrics

-
- Error loading metrics -
-
- ); - } - - const memoryPercentage = calculateMemoryPercentage( - data.memory_usage_bytes, - ); - - return ( -
-
-

Container Metrics

- -
- - {/* Main Metrics */} -
-
-
-
CPU Usage
-
- {cpuPercentage} - {metricsData.rawData?.cpu_usage_seconds_total && - metricsData.rawData.cpu_usage_seconds_total.length > 0 ? ( - - ) : null} -
-
-
-
Memory Usage
-
- - {formatBytes(data.memory_usage_bytes)} - {memoryPercentage !== null && ( - - ({memoryPercentage.toFixed(1)}%) - - )} - - {metricsData.rawData?.memory_usage_bytes && - metricsData.rawData.memory_usage_bytes.length > 0 ? ( - - ) : null} -
-
-
-
- - {/* Advanced Metrics */} - {false && ( - - {/* CPU & Performance */} -
-

CPU & Performance

-
-
CPU Load Average (10s)
-
{formatCpuUsage(data.cpu_load_average_10s)}
-
CPU Usage Seconds Total
-
- {formatNumber(data.cpu_usage_seconds_total)} -
-
CPU User Seconds Total
-
{formatNumber(data.cpu_user_seconds_total)}
-
CPU System Seconds Total
-
- {formatNumber(data.cpu_system_seconds_total)} -
-
CPU Schedstat Run Periods
-
- {formatNumber( - data.cpu_schedstat_run_periods_total, - )} -
-
CPU Schedstat Run Seconds
-
- {formatNumber( - data.cpu_schedstat_run_seconds_total, - )} -
-
CPU Schedstat Runqueue Seconds
-
- {formatNumber( - data.cpu_schedstat_runqueue_seconds_total, - )} -
-
-
- - {/* Memory */} -
-

Memory

-
-
Memory Usage
-
{formatBytes(data.memory_usage_bytes)}
-
Memory Working Set
-
- {formatBytes(data.memory_working_set_bytes)} -
-
Memory RSS
-
{formatBytes(data.memory_rss)}
-
Memory Cache
-
{formatBytes(data.memory_cache)}
-
Memory Swap
-
{formatBytes(data.memory_swap)}
-
Memory Max Usage
-
{formatBytes(data.memory_max_usage_bytes)}
-
Memory Mapped File
-
{formatBytes(data.memory_mapped_file)}
-
Memory Failcnt
-
{formatNumber(data.memory_failcnt)}
-
-
- - {/* Memory Failures */} -
-

Memory Failures

-
-
Page Fault (Container)
-
- {formatNumber( - data.memory_failures_pgfault_container, - )} -
-
Page Fault (Hierarchy)
-
- {formatNumber( - data.memory_failures_pgfault_hierarchy, - )} -
-
Major Page Fault (Container)
-
- {formatNumber( - data.memory_failures_pgmajfault_container, - )} -
-
Major Page Fault (Hierarchy)
-
- {formatNumber( - data.memory_failures_pgmajfault_hierarchy, - )} -
-
-
- - {/* Resource Limits */} -
-

Resource Limits

-
-
Memory Limit
-
{resources?.memory ? `${resources.memory} MB` : "n/a"}
-
CPU Limit
-
{resources?.cpu ? `${resources.cpu / 1000} cores` : "n/a"}
-
-
- - {/* Processes & Threads */} -
-

- Processes & Threads -

-
-
Processes
-
{formatNumber(data.processes)}
-
Threads
-
{formatNumber(data.threads)}
-
Max Threads
-
{formatNumber(data.threads_max)}
-
Tasks Running
-
{formatNumber(data.tasks_state_running)}
-
Tasks Sleeping
-
{formatNumber(data.tasks_state_sleeping)}
-
Tasks Stopped
-
{formatNumber(data.tasks_state_stopped)}
-
Tasks IO Waiting
-
{formatNumber(data.tasks_state_iowaiting)}
-
Tasks Uninterruptible
-
- {formatNumber(data.tasks_state_uninterruptible)} -
-
-
- - {/* Filesystem */} -
-

Filesystem

-
-
Reads Bytes Total (sda)
-
- {formatBytes(data.fs_reads_bytes_total_sda)} -
-
Writes Bytes Total (sda)
-
- {formatBytes(data.fs_writes_bytes_total_sda)} -
-
-
- - {/* Network - Receive */} -
-

Network - Receive

-
-
Bytes Total (eth0)
-
- {formatBytes( - data.network_receive_bytes_total_eth0, - )} -
-
Bytes Total (eth1)
-
- {formatBytes( - data.network_receive_bytes_total_eth1, - )} -
-
Errors Total (eth0)
-
- {formatNumber( - data.network_receive_errors_total_eth0, - )} -
-
Errors Total (eth1)
-
- {formatNumber( - data.network_receive_errors_total_eth1, - )} -
-
Packets Dropped (eth0)
-
- {formatNumber( - data.network_receive_packets_dropped_total_eth0, - )} -
-
Packets Dropped (eth1)
-
- {formatNumber( - data.network_receive_packets_dropped_total_eth1, - )} -
-
Packets Total (eth0)
-
- {formatNumber( - data.network_receive_packets_total_eth0, - )} -
-
Packets Total (eth1)
-
- {formatNumber( - data.network_receive_packets_total_eth1, - )} -
-
-
- - {/* Network - Transmit */} -
-

Network - Transmit

-
-
Bytes Total (eth0)
-
- {formatBytes( - data.network_transmit_bytes_total_eth0, - )} -
-
Bytes Total (eth1)
-
- {formatBytes( - data.network_transmit_bytes_total_eth1, - )} -
-
Errors Total (eth0)
-
- {formatNumber( - data.network_transmit_errors_total_eth0, - )} -
-
Errors Total (eth1)
-
- {formatNumber( - data.network_transmit_errors_total_eth1, - )} -
-
Packets Dropped (eth0)
-
- {formatNumber( - data.network_transmit_packets_dropped_total_eth0, - )} -
-
Packets Dropped (eth1)
-
- {formatNumber( - data.network_transmit_packets_dropped_total_eth1, - )} -
-
Packets Total (eth0)
-
- {formatNumber( - data.network_transmit_packets_total_eth0, - )} -
-
Packets Total (eth1)
-
- {formatNumber( - data.network_transmit_packets_total_eth1, - )} -
-
-
- - {/* TCP Connections */} -
-

TCP Connections

-
-
Close
-
- {formatNumber(data.network_tcp_usage_close)} -
-
Close Wait
-
- {formatNumber( - data.network_tcp_usage_closewait, - )} -
-
Closing
-
- {formatNumber(data.network_tcp_usage_closing)} -
-
Established
-
- {formatNumber( - data.network_tcp_usage_established, - )} -
-
Fin Wait 1
-
- {formatNumber(data.network_tcp_usage_finwait1)} -
-
Fin Wait 2
-
- {formatNumber(data.network_tcp_usage_finwait2)} -
-
Last Ack
-
- {formatNumber(data.network_tcp_usage_lastack)} -
-
Listen
-
- {formatNumber(data.network_tcp_usage_listen)} -
-
Syn Recv
-
- {formatNumber(data.network_tcp_usage_synrecv)} -
-
Syn Sent
-
- {formatNumber(data.network_tcp_usage_synsent)} -
-
Time Wait
-
- {formatNumber(data.network_tcp_usage_timewait)} -
-
-
- - {/* TCP6 Connections */} -
-

TCP6 Connections

-
-
Close
-
- {formatNumber(data.network_tcp6_usage_close)} -
-
Close Wait
-
- {formatNumber( - data.network_tcp6_usage_closewait, - )} -
-
Closing
-
- {formatNumber(data.network_tcp6_usage_closing)} -
-
Established
-
- {formatNumber( - data.network_tcp6_usage_established, - )} -
-
Fin Wait 1
-
- {formatNumber(data.network_tcp6_usage_finwait1)} -
-
Fin Wait 2
-
- {formatNumber(data.network_tcp6_usage_finwait2)} -
-
Last Ack
-
- {formatNumber(data.network_tcp6_usage_lastack)} -
-
Listen
-
- {formatNumber(data.network_tcp6_usage_listen)} -
-
Syn Recv
-
- {formatNumber(data.network_tcp6_usage_synrecv)} -
-
Syn Sent
-
- {formatNumber(data.network_tcp6_usage_synsent)} -
-
Time Wait
-
- {formatNumber(data.network_tcp6_usage_timewait)} -
-
-
- - {/* UDP Connections */} -
-

UDP Connections

-
-
Dropped
-
- {formatNumber(data.network_udp_usage_dropped)} -
-
Listen
-
- {formatNumber(data.network_udp_usage_listen)} -
-
RX Queued
-
- {formatNumber(data.network_udp_usage_rxqueued)} -
-
TX Queued
-
- {formatNumber(data.network_udp_usage_txqueued)} -
-
-
- - {/* UDP6 Connections */} -
-

UDP6 Connections

-
-
Dropped
-
- {formatNumber(data.network_udp6_usage_dropped)} -
-
Listen
-
- {formatNumber(data.network_udp6_usage_listen)} -
-
RX Queued
-
- {formatNumber(data.network_udp6_usage_rxqueued)} -
-
TX Queued
-
- {formatNumber(data.network_udp6_usage_txqueued)} -
-
-
- - {/* System */} -
-

System

-
-
File Descriptors
-
{formatNumber(data.file_descriptors)}
-
Sockets
-
{formatNumber(data.sockets)}
-
Last Seen
-
{formatTimestamp(data.last_seen)}
-
Start Time
-
{formatTimestamp(data.start_time_seconds)}
-
-
-
- )} -
- ); -} \ No newline at end of file diff --git a/frontend/packages/components/src/actors/actor-network.tsx b/frontend/packages/components/src/actors/actor-network.tsx deleted file mode 100644 index 3bb466ad99..0000000000 --- a/frontend/packages/components/src/actors/actor-network.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { - Button, - Dd, - DiscreteCopyButton, - Dl, - DocsSheet, - Dt, - Flex, - cn, -} from "@rivet-gg/components"; -import { Icon, faBooks } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { Fragment } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorObjectInspector } from "./console/actor-inspector"; - -const selector = (a: Actor) => a.network?.ports; - -export interface ActorNetworkProps { - actor: ActorAtom; -} - -export function ActorNetwork({ actor }: ActorNetworkProps) { - const ports = useAtomValue(selectAtom(actor, selector)); - if (!ports) { - return null; - } - - return ( -
-
-

Network

- - - -
-
- -
-
Ports
-
- {Object.entries(ports).map( - ([name, port], index) => ( - - - {name} - -
-
Protocol
-
- - {port.protocol} - -
-
Port
-
- - {port.port} - -
-
Hostname
-
- - - {port.hostname} - - -
- {port.url ? ( - <> -
URL
-
- - - {port.url} - - -
- - ) : null} - - {port.routing?.host ? ( - <> -
Host Routing
-
- - - -
- - ) : null} -
-
- ), - )} -
-
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-not-found.tsx b/frontend/packages/components/src/actors/actor-not-found.tsx deleted file mode 100644 index c1beda2464..0000000000 --- a/frontend/packages/components/src/actors/actor-not-found.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Icon, faQuestionSquare } from "@rivet-gg/icons"; -import { useAtomValue, useSetAtom } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { Button } from "../ui/button"; -import { FilterOp } from "../ui/filters"; -import { - type ActorFeature, - actorFiltersAtom, - currentActorQueryAtom, -} from "./actor-context"; -import { ActorTabs } from "./actors-actor-details"; -import { useActorsView } from "./actors-view-context-provider"; -import { ShimmerLine } from "../shimmer-line"; - -export function ActorNotFound({ - features = [], -}: { features?: ActorFeature[] }) { - const { copy } = useActorsView(); - - const setFilters = useSetAtom(actorFiltersAtom); - const hasDevMode = useAtomValue( - selectAtom( - actorFiltersAtom, - useCallback((filters) => filters.devMode, []), - ), - ); - - const { isLoading } = useAtomValue(currentActorQueryAtom); - - return ( -
- -
- {!isLoading ? ( - <> - -

- {copy.actorNotFound} -

-

- {copy.actorNotFoundDescription} -

- - ) : null} - - {!hasDevMode && !isLoading ? ( - - ) : null} - {isLoading ? : null} -
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/actor-region.tsx b/frontend/packages/components/src/actors/actor-region.tsx deleted file mode 100644 index 12b7eb3e2e..0000000000 --- a/frontend/packages/components/src/actors/actor-region.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Flex, WithTooltip } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { useCallback } from "react"; -import { - REGION_LABEL, - RegionIcon, - getRegionKey, -} from "../matchmaker/lobby-region"; -import { actorRegionsAtom } from "./actor-context"; - -interface ActorRegionProps { - regionId?: string; - showLabel?: boolean | "abbreviated"; - className?: string; -} - -export function ActorRegion({ - showLabel, - regionId, - className, -}: ActorRegionProps) { - const region = useAtomValue( - selectAtom( - actorRegionsAtom, - useCallback( - (regions) => regions.find((region) => region.id === regionId), - [regionId], - ), - ), - ); - - const regionKey = getRegionKey(region?.id); - - if (showLabel) { - return ( - - - - {showLabel === "abbreviated" - ? regionKey.toUpperCase() - : (REGION_LABEL[regionKey] ?? REGION_LABEL.unknown)} - - - ); - } - - return ( - - - - } - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-runtime.tsx b/frontend/packages/components/src/actors/actor-runtime.tsx deleted file mode 100644 index e8c0f181a8..0000000000 --- a/frontend/packages/components/src/actors/actor-runtime.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { Suspense } from "react"; -import { formatDuration } from "../lib/formatter"; -import { toRecord } from "../lib/utils"; -import { Flex } from "../ui/flex"; -import { Skeleton } from "../ui/skeleton"; -import { Dd, Dl, Dt } from "../ui/typography"; -import { ActorBuild } from "./actor-build"; -import { - type Actor, - type ActorAtom, - ActorFeature, - currentActorFeaturesAtom, -} from "./actor-context"; -import { ACTOR_FRAMEWORK_TAG_VALUE } from "./actor-tags"; -import { ActorObjectInspector } from "./console/actor-inspector"; - -const selector = (a: Actor) => ({ - lifecycle: a.lifecycle, - resources: a.resources, - runtime: a.runtime, - tags: a.tags, -}); - -export interface ActorRuntimeProps { - actor: ActorAtom; -} - -export function ActorRuntime({ actor }: ActorRuntimeProps) { - const { lifecycle, resources, runtime, tags } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - const features = useAtomValue(currentActorFeaturesAtom); - - return ( - <> - {features.includes(ActorFeature.Runtime) && lifecycle && runtime ? ( -
-
-

Runtime

-
- -
-
Kill timeout
-
- {formatDuration(lifecycle.killTimeout || 0, { - show0Min: true, - })} -
- {toRecord(tags).framework !== - ACTOR_FRAMEWORK_TAG_VALUE && resources ? ( - <> -
Resources
-
- {resources.cpu / 1000} CPU cores,{" "} - {resources.memory} MB RAM -
- - ) : null} -
Arguments
-
- -
-
Environment
-
- -
- -
Durable
-
{lifecycle.durable ? "Yes" : "No"}
-
-
-
- ) : null} - - } - > - - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-state-change-indicator.tsx b/frontend/packages/components/src/actors/actor-state-change-indicator.tsx deleted file mode 100644 index b5d1400d85..0000000000 --- a/frontend/packages/components/src/actors/actor-state-change-indicator.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Badge } from "@rivet-gg/components"; -import { motion } from "framer-motion"; -import { useEffect, useRef } from "react"; - -const EMPTY_OBJECT = {}; - -interface ActorStateChangeIndicatorProps { - state: unknown | undefined; -} - -export function ActorStateChangeIndicator({ - state, -}: ActorStateChangeIndicatorProps) { - const isMounted = useRef(false); - const oldState = useRef(); - - useEffect(() => { - isMounted.current = true; - }, []); - - useEffect(() => { - oldState.current = state || EMPTY_OBJECT; - }, [state]); - - const hasChanged = state !== oldState.current; - const shouldUpdate = hasChanged && isMounted.current; - - return ( - - - State changed - - - ); -} diff --git a/frontend/packages/components/src/actors/actor-state-tab.tsx b/frontend/packages/components/src/actors/actor-state-tab.tsx deleted file mode 100644 index 634596a9b5..0000000000 --- a/frontend/packages/components/src/actors/actor-state-tab.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import { ActorEditableState } from "./actor-editable-state"; -import { - useActorState, - useActorWorkerStatus, -} from "./worker/actor-worker-context"; - -const selector = (a: Actor) => a.destroyedAt; - -interface ActorStateTabProps { - actor: ActorAtom; -} - -export function ActorStateTab({ actor }: ActorStateTabProps) { - const destroyedAt = useAtomValue(selectAtom(actor, selector)); - const status = useActorWorkerStatus(); - - const state = useActorState(); - - if (destroyedAt) { - return ( -
- State Preview is unavailable for inactive Actors. -
- ); - } - - if (status.type === "error") { - return ( -
- State Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (status.type === "unsupported") { - return ( -
- State Preview is not supported for this Actor. -
- ); - } - - if (status.type !== "ready") { - return ( -
- Loading state... -
- ); - } - - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/actor-status-indicator.tsx b/frontend/packages/components/src/actors/actor-status-indicator.tsx deleted file mode 100644 index 81f9ff34dc..0000000000 --- a/frontend/packages/components/src/actors/actor-status-indicator.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Ping, cn } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { ComponentPropsWithRef } from "react"; -import type { Actor, ActorAtom } from "./actor-context"; - -export type ActorStatus = - | "starting" - | "running" - | "stopped" - | "crashed" - | "unknown"; - -export function getActorStatus( - actor: Pick, -): ActorStatus { - const { createdAt, startedAt, destroyedAt } = actor; - - if (createdAt && !startedAt && !destroyedAt) { - return "starting"; - } - - if (createdAt && startedAt && !destroyedAt) { - return "running"; - } - - if (createdAt && startedAt && destroyedAt) { - return "stopped"; - } - - if (createdAt && !startedAt && destroyedAt) { - return "crashed"; - } - - return "unknown"; -} - -interface AtomizedActorStatusIndicatorProps - extends ComponentPropsWithRef<"span"> { - actor: ActorAtom; -} - -export const AtomizedActorStatusIndicator = ({ - actor, - ...props -}: AtomizedActorStatusIndicatorProps) => { - const status = useAtomValue(selectAtom(actor, selector)); - return ; -}; - -const selector = ({ status }: Actor) => status; - -interface ActorStatusIndicatorProps extends ComponentPropsWithRef<"span"> { - status: ReturnType; -} - -export const ActorStatusIndicator = ({ - status, - ...props -}: ActorStatusIndicatorProps) => { - if (status === "running") { - return ( - - ); - } - - return ( - - ); -}; diff --git a/frontend/packages/components/src/actors/actor-status-label.tsx b/frontend/packages/components/src/actors/actor-status-label.tsx deleted file mode 100644 index c6a16ddb7d..0000000000 --- a/frontend/packages/components/src/actors/actor-status-label.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom } from "./actor-context"; -import type { ActorStatus } from "./actor-status-indicator"; - -export const ACTOR_STATUS_LABEL_MAP = { - unknown: "Unknown", - starting: "Starting", - running: "Running", - stopped: "Stopped", - crashed: "Crashed", -} satisfies Record; - -export const ActorStatusLabel = ({ status }: { status: ActorStatus }) => { - return {ACTOR_STATUS_LABEL_MAP[status]}; -}; - -const selector = (a: Actor) => a.status; - -export const AtomizedActorStatusLabel = ({ - actor, -}: { - actor: ActorAtom; -}) => { - const status = useAtomValue(selectAtom(actor, selector)); - return ; -}; diff --git a/frontend/packages/components/src/actors/actor-status.tsx b/frontend/packages/components/src/actors/actor-status.tsx deleted file mode 100644 index 0f587ed7cb..0000000000 --- a/frontend/packages/components/src/actors/actor-status.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import type { ActorAtom } from "./actor-context"; -import { - ActorStatusIndicator, - type ActorStatus as ActorStatusType, - AtomizedActorStatusIndicator, -} from "./actor-status-indicator"; -import { - ActorStatusLabel, - AtomizedActorStatusLabel, -} from "./actor-status-label"; - -interface ActorStatusProps { - className?: string; - actor: ActorAtom; -} - -export const AtomizedActorStatus = ({ - className, - ...props -}: ActorStatusProps) => { - return ( -
- - -
- ); -}; - -export const ActorStatus = ({ - className, - status, -}: { - className?: string; - status: ActorStatusType; -}) => { - return ( -
- - -
- ); -}; diff --git a/frontend/packages/components/src/actors/actor-stop-button.tsx b/frontend/packages/components/src/actors/actor-stop-button.tsx deleted file mode 100644 index cbc67ea89f..0000000000 --- a/frontend/packages/components/src/actors/actor-stop-button.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Button, WithTooltip } from "@rivet-gg/components"; -import { Icon, faXmark } from "@rivet-gg/icons"; - -import equal from "fast-deep-equal"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import type { Actor, ActorAtom, DestroyActorAtom } from "./actor-context"; - -const selector = (a: Actor) => ({ - destroyedAt: a.destroyedAt, - destroy: a.destroy, -}); - -interface ActorStopButtonProps { - actor: ActorAtom; -} - -export function ActorStopButton({ actor }: ActorStopButtonProps) { - const { destroy: destroyAtom, destroyedAt } = useAtomValue( - selectAtom(actor, selector, equal), - ); - - if (destroyedAt || !destroyAtom) { - return null; - } - - return ; -} - -function Content({ destroy: destroyAtom }: { destroy: DestroyActorAtom }) { - const { destroy, isDestroying } = useAtomValue(destroyAtom); - return ( - - - - } - content="Stop Actor" - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-tags-select.tsx b/frontend/packages/components/src/actors/actor-tags-select.tsx deleted file mode 100644 index 17eedbba3e..0000000000 --- a/frontend/packages/components/src/actors/actor-tags-select.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { useMemo } from "react"; -import { actorTagsAtom } from "./actor-context"; -import { ActorTag } from "./actor-tags"; - -interface ActorTagsSelectProps { - value: Record; - onValueChange: (value: Record) => void; - showSelectedOptions?: number; -} - -export function ActorTagsSelect({ - value, - onValueChange, - showSelectedOptions, -}: ActorTagsSelectProps) { - const data = useAtomValue(actorTagsAtom); - - const valArray = useMemo(() => Object.entries(value), [value]); - const tags = useMemo(() => { - // upsert custom tags to the list of tags - const tags = [...data]; - for (const [key, value] of valArray) { - const found = data.find( - (tag) => tag.key === key && tag.value === value, - ); - - if (!found) { - tags.push({ key, value }); - } - } - return tags; - }, [valArray, data]); - - const val = useMemo( - () => - valArray.map(([key, value]) => { - return [key, value].join("="); - }), - [valArray], - ); - - const options = useMemo( - () => - tags.map((tag) => { - return { - label: ( - - - {tag.key}={tag.value} - - - ), - value: [tag.key, tag.value].join("="), - tag, - }; - }), - [tags], - ); - - const handleValueChange = (value: string[]) => { - onValueChange( - Object.fromEntries( - value.map((v) => { - // its safe to split by "=" because the value is a tag - const [key, value] = v.split("="); - return [key.trim(), value.trim()]; - }), - ), - ); - }; - - const handleCreateOption = (option: string) => { - const parts = option.split("="); - if (parts.length !== 2) return; - - const [key, value] = parts.map((part) => part.trim()); - if (!key || !value) return; - - onValueChange(Object.fromEntries([...valArray, [key, value]])); - }; - - return ( - { - const tagKey = option.tag.key.toLowerCase(); - const tagValue = option.tag.value.toLowerCase(); - - if (search.includes("=")) { - const [key, value] = search.split("="); - return tagKey.includes(key) && tagValue.includes(value); - } - return tagKey.includes(search) || tagValue.includes(search); - }} - className="w-full min-w-[20rem]" - /> - ); -} diff --git a/frontend/packages/components/src/actors/actor-tags.tsx b/frontend/packages/components/src/actors/actor-tags.tsx deleted file mode 100644 index 5a017d5cb0..0000000000 --- a/frontend/packages/components/src/actors/actor-tags.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { - Button, - DiscreteCopyButton, - Slot, - Slottable, - WithTooltip, - cn, -} from "@rivet-gg/components"; -import { Icon, faTag } from "@rivet-gg/icons"; -import { type ReactNode, forwardRef, useState } from "react"; - -const BUILT_IN_TAGS = { - actors: ["framework", "framework-version"], - builds: ["current"], -}; - -export const ACTOR_FRAMEWORK_TAG_VALUE = "rivetkit"; - -export const ActorTag = forwardRef< - HTMLSpanElement, - { children: ReactNode; className?: string } ->(({ children, className, ...props }, ref) => ( - - - {children} - -)); - -interface ActorTagsProps { - tags?: unknown; - excludeBuiltIn?: keyof typeof BUILT_IN_TAGS; - className?: string; - truncate?: boolean; - copy?: boolean; - max?: number; - hoverable?: boolean; -} - -export function ActorTags({ - tags = {}, - excludeBuiltIn = undefined, - truncate = true, - className, - hoverable = true, - max = Number.POSITIVE_INFINITY, - copy = true, -}: ActorTagsProps) { - const withoutBuiltIn = Object.entries(tags ?? {}).filter(([key]) => - excludeBuiltIn ? !BUILT_IN_TAGS[excludeBuiltIn].includes(key) : true, - ); - - const [isTruncatedList, setTruncatedList] = useState( - withoutBuiltIn.length > max, - ); - - const truncated = withoutBuiltIn.filter((_, index) => - isTruncatedList ? index < max : true, - ); - - const truncatedCount = withoutBuiltIn.length - truncated.length; - - return ( -
- {truncated.length > 0 ? ( - <> - {truncated - .sort(([a], [b]) => a.localeCompare(b)) - .map(([key, value]) => { - let trigger = truncate ? ( - - - {key}={value} - - - ) : ( - - - {key}={value} - - - ); - - trigger = copy ? ( - - - {trigger} - - - ) : ( - trigger - ); - - return truncate && hoverable && !copy ? ( - - ) : ( - trigger - ); - })} - - {truncatedCount > 0 ? ( - - ) : null} - - ) : null} -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-actor-details.tsx b/frontend/packages/components/src/actors/actors-actor-details.tsx deleted file mode 100644 index e45a7bc48e..0000000000 --- a/frontend/packages/components/src/actors/actors-actor-details.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { - Flex, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - cn, -} from "@rivet-gg/components"; -import { Icon, faQuestionSquare } from "@rivet-gg/icons"; -import { useAtomValue } from "jotai"; -import { type ReactNode, Suspense, memo } from "react"; -import { ActorConfigTab } from "./actor-config-tab"; -import { ActorConnectionsTab } from "./actor-connections-tab"; -import { - type ActorAtom, - ActorFeature, - currentActorFeaturesAtom, -} from "./actor-context"; -import { ActorDetailsSettingsProvider } from "./actor-details-settings"; -import { ActorLogsTab } from "./actor-logs-tab"; -import { ActorMetricsTab } from "./actor-metrics-tab"; -import { ActorStateTab } from "./actor-state-tab"; -import { AtomizedActorStatus } from "./actor-status"; -import { ActorStopButton } from "./actor-stop-button"; -import { ActorsSidebarToggleButton } from "./actors-sidebar-toggle-button"; -import { useActorsView } from "./actors-view-context-provider"; -import { ActorConsole } from "./console/actor-console"; -import { ActorWorkerContextProvider } from "./worker/actor-worker-context"; - -interface ActorsActorDetailsProps { - tab?: string; - actor: ActorAtom; - onTabChange?: (tab: string) => void; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExportingLogs?: boolean; -} - -export const ActorsActorDetails = memo( - ({ - tab, - onTabChange, - actor, - onExportLogs, - isExportingLogs, - }: ActorsActorDetailsProps) => { - const actorFeatures = useAtomValue(currentActorFeaturesAtom); - const supportsConsole = actorFeatures?.includes(ActorFeature.Console); - - return ( - - -
- - - {supportsConsole ? : null} -
-
-
- ); - }, -); - -export const ActorsActorEmptyDetails = ({ - features, -}: { - features: ActorFeature[]; -}) => { - const { copy } = useActorsView(); - return ( -
- -
- -

{copy.selectActor}

-
-
-
- ); -}; - -export function ActorTabs({ - tab, - features, - onTabChange, - actor, - className, - disabled, - children, - onExportLogs, - isExportingLogs, -}: { - disabled?: boolean; - tab?: string; - features: ActorFeature[]; - onTabChange?: (tab: string) => void; - actor?: ActorAtom; - className?: string; - children?: ReactNode; - onExportLogs?: ( - actorId: string, - typeFilter?: string, - filter?: string, - ) => Promise; - isExportingLogs?: boolean; -}) { - const supportsState = features?.includes(ActorFeature.State); - const supportsLogs = features?.includes(ActorFeature.Logs); - const supportsConnections = features?.includes(ActorFeature.Connections); - const supportsConfig = features?.includes(ActorFeature.Config); - const supportsMetrics = features?.includes(ActorFeature.Metrics); - - const defaultTab = supportsState ? "state" : "logs"; - const value = disabled ? undefined : tab || defaultTab; - - return ( - -
- -
- - {supportsState ? ( - - State - - ) : null} - {supportsConnections ? ( - - Connections - - ) : null} - {supportsLogs ? ( - - Logs - - ) : null} - {supportsConfig ? ( - - Config - - ) : null} - {supportsMetrics ? ( - - Metrics - - ) : null} - - {actor ? ( - - - - - ) : null} -
-
- {actor ? ( - <> - {supportsLogs ? ( - - }> - - - - ) : null} - {supportsConfig ? ( - - - - ) : null} - {supportsConnections ? ( - - - - ) : null} - {supportsState ? ( - - - - ) : null} - {supportsMetrics ? ( - - - - ) : null} - - ) : null} - {children} -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-actor-not-found.tsx b/frontend/packages/components/src/actors/actors-actor-not-found.tsx deleted file mode 100644 index 96fed3be76..0000000000 --- a/frontend/packages/components/src/actors/actors-actor-not-found.tsx +++ /dev/null @@ -1,33 +0,0 @@ -// import { isRivetError } from "@/lib/utils"; -// import { RivetError } from "@rivet-gg/api"; -import { Icon, faCircleExclamation } from "@rivet-gg/icons"; -import type { ErrorComponentProps } from "@tanstack/react-router"; -import { ActorsSidebarToggleButton } from "./actors-sidebar-toggle-button"; - -export function ActorsActorError({ error }: ErrorComponentProps) { - // if (isRivetError(error) || error instanceof RivetError) { - // return ( - //
- //
- // - //
- //
- // - // Actor not found. - //
- //
- // ); - // } - - return ( -
-
- -
-
- - Error occurred while fetching Actor. -
-
- ); -} diff --git a/frontend/packages/components/src/actors/actors-layout-context.tsx b/frontend/packages/components/src/actors/actors-layout-context.tsx deleted file mode 100644 index a40a84c760..0000000000 --- a/frontend/packages/components/src/actors/actors-layout-context.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { ReactNode } from "react"; -import { createContext, useContext, useMemo } from "react"; -import { assertNonNullable } from "../lib/utils"; - -export interface ActorsLayoutContextValue { - isFolded: boolean; - setFolded: (value: boolean) => void; -} - -export const ActorsLayoutContext = - createContext(null); - -interface ActorsLayoutProviderProps extends ActorsLayoutContextValue { - children: ReactNode; -} - -export function ActorsLayoutContextProvider({ - children, - isFolded, - setFolded, -}: ActorsLayoutProviderProps) { - return ( - ({ isFolded, setFolded }), - [isFolded, setFolded], - )} - > - {children} - - ); -} - -export function useActorsLayout() { - const context = useContext(ActorsLayoutContext); - assertNonNullable(context); - return context; -} diff --git a/frontend/packages/components/src/actors/actors-layout.tsx b/frontend/packages/components/src/actors/actors-layout.tsx deleted file mode 100644 index df2a8d4b32..0000000000 --- a/frontend/packages/components/src/actors/actors-layout.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { type ReactNode, memo, useState } from "react"; -import { cn, ls } from "../lib/utils"; -import { ActorsLayoutContextProvider } from "./actors-layout-context"; - -interface ActorsListPreviewProps { - left: ReactNode; - right: ReactNode; - className?: string; -} - -export const ActorsLayout = memo( - ({ left, right, className }: ActorsListPreviewProps) => { - const [folded, setFolded] = useState(() => ls.actorsList.getFolded()); - - return ( - -
- {left} -
- {right} -
-
-
- ); - }, -); diff --git a/frontend/packages/components/src/actors/actors-list-panel.tsx b/frontend/packages/components/src/actors/actors-list-panel.tsx deleted file mode 100644 index aea9d29000..0000000000 --- a/frontend/packages/components/src/actors/actors-list-panel.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ActorsList } from "./actors-list"; - -export function ActorsListPanel() { - return ; -} diff --git a/frontend/packages/components/src/actors/actors-list-preview.tsx b/frontend/packages/components/src/actors/actors-list-preview.tsx deleted file mode 100644 index d26a22acab..0000000000 --- a/frontend/packages/components/src/actors/actors-list-preview.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { Icon, faGripDotsVertical } from "@rivet-gg/icons"; -import { - animate, - motion, - useMotionTemplate, - useMotionValue, - useMotionValueEvent, - useTransform, -} from "framer-motion"; -import { - type ReactNode, - Suspense, - memo, - useCallback, - useLayoutEffect, - useState, -} from "react"; -import { cn, ls } from "../lib/utils"; -import { ActorsLayoutContextProvider } from "./actors-layout-context"; -import { ActorsListPanel } from "./actors-list-panel"; - -const RIGHT_PANEL_MIN_WIDTH = 480; - -interface ActorsListPreviewProps { - children: ReactNode; -} - -export const ActorsListPreview = memo( - ({ children }: ActorsListPreviewProps) => { - const outerWidth = useMotionValue(0); - const x = useMotionValue(0); - - const rightWidth = useMotionTemplate`calc(50% - ${x}px)`; - const leftWidth = useMotionTemplate`calc(50% + ${x}px)`; - - const [folded, setFolded] = useState(() => ls.actorsList.getFolded()); - const [isDragging, setIsDragging] = useState(false); - - const [, setInitialized] = useState(false); - - useMotionValueEvent(x, "change", (value) => { - ls.actorsList.set(value / outerWidth.get(), folded); - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: on first draw - useLayoutEffect(() => { - x.setCurrent((ls.actorsList.getWidth() || 0) * outerWidth.get()); - setInitialized(true); - }, []); - - const relativeOffset = useTransform( - [x, outerWidth], - ([value, outerWidth]: number[]) => { - const center = outerWidth / 2; - const percent = (value + center) / outerWidth; - // 0.5 is the center - // 0 is the left - // 1 is the right - return percent; - }, - ); - - const opacity = useTransform(relativeOffset, [0, 0.1], [0, 1]); - const pointerEvents = useTransform(opacity, () => { - return opacity.get() > 0.5 ? "auto" : "none"; - }); - - const toggle = useCallback( - (newValue: boolean) => { - setFolded(newValue); - if (newValue) { - animate(x, -outerWidth.get() / 2); - } else { - animate(x, 0); - } - }, - [outerWidth, x], - ); - - return ( - -
{ - if (ref) { - const width = ref.getBoundingClientRect().width; - outerWidth.set(width); - } - }} - > - - - - setIsDragging(true)} - onDrag={(e, info) => { - const rightPos = outerWidth.get() - info.point.x; - setFolded(outerWidth.get() - rightPos < 470); - }} - onDoubleClick={() => { - setFolded(!folded); - if (folded) { - animate(x, 0); - } else { - animate(x, -outerWidth.get() / 2); - } - }} - onDragEnd={(e, info) => { - if (folded) { - animate(x, -outerWidth.get() / 2); - } else { - const leftPos = outerWidth.get() - info.point.x; - if (leftPos < RIGHT_PANEL_MIN_WIDTH) { - animate( - x, - outerWidth.get() / 2 - - RIGHT_PANEL_MIN_WIDTH, - ); - } - } - setIsDragging(false); - }} - dragMomentum={false} - className="w-[1px] bg-border cursor-col-resize inset-y-0 z-20 relative flex items-center group" - > - - - - {children} - -
-
- ); - }, -); diff --git a/frontend/packages/components/src/actors/actors-list-row.tsx b/frontend/packages/components/src/actors/actors-list-row.tsx deleted file mode 100644 index 6108b4df0c..0000000000 --- a/frontend/packages/components/src/actors/actors-list-row.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { - Button, - RelativeTime, - SmallText, - WithTooltip, - cn, - toRecord, -} from "@rivet-gg/components"; -import { Icon, faTag, faTags } from "@rivet-gg/icons"; -import { Link } from "@tanstack/react-router"; -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { memo } from "react"; -import { - type Actor, - type ActorAtom, - isCurrentActorAtom, -} from "./actor-context"; -import { ActorRegion } from "./actor-region"; -import { AtomizedActorStatusIndicator } from "./actor-status-indicator"; -import { AtomizedActorStatusLabel } from "./actor-status-label"; -import { ActorTags } from "./actor-tags"; - -interface ActorsListRowProps { - className?: string; - actor: ActorAtom; -} - -const selector = (actor: Actor) => actor.id; - -export const ActorsListRow = memo( - ({ className, actor }: ActorsListRowProps) => { - const id = useAtomValue(selectAtom(actor, selector)); - const isCurrent = useAtomValue(isCurrentActorAtom(actor)); - - return ( - - ); - }, -); - -const regionSelector = (actor: Actor) => actor.region; - -function Region({ actor }: { actor: ActorAtom }) { - const regionId = useAtomValue(selectAtom(actor, regionSelector)); - - return ( - - ); -} - -const tagsSelector = (actor: Actor) => toRecord(actor.tags); - -function Tags({ actor }: { actor: ActorAtom }) { - const tags = useAtomValue(selectAtom(actor, tagsSelector)); - - const tagCount = Object.keys(tags).length; - - return ( - - -
- - {Object.keys(tags).length}{" "} - {tagCount === 1 ? "tag" : "tags"} -
- - } - content={ - <> -

Tags

- - - } - /> - ); -} - -const createdAtSelector = (actor: Actor) => actor.createdAt; - -function CreatedAt({ actor }: { actor: ActorAtom }) { - const createdAt = useAtomValue(selectAtom(actor, createdAtSelector)); - - return ( - - {createdAt ? ( - } - content={createdAt.toLocaleString()} - /> - ) : ( - - - )} - - ); -} - -const destroyedAtSelector = (actor: Actor) => actor.destroyedAt; -function DestroyedAt({ actor }: { actor: ActorAtom }) { - const destroyedAt = useAtomValue(selectAtom(actor, destroyedAtSelector)); - - return ( - - {destroyedAt ? ( - } - content={new Date(destroyedAt).toLocaleString()} - /> - ) : ( - - - )} - - ); -} diff --git a/frontend/packages/components/src/actors/actors-list.tsx b/frontend/packages/components/src/actors/actors-list.tsx deleted file mode 100644 index 2ca0e8242d..0000000000 --- a/frontend/packages/components/src/actors/actors-list.tsx +++ /dev/null @@ -1,489 +0,0 @@ -import { - Button, - Checkbox, - CommandGroup, - CommandItem, - DocsSheet, - FilterCreator, - type FilterDefinitions, - FilterOp, - type OnFiltersChange, - type OptionsProviderProps, - ScrollArea, - ShimmerLine, - SmallText, - cn, - createFiltersPicker, - createFiltersSchema, -} from "@rivet-gg/components"; -import { - Icon, - faActors, - faCalendarCircleMinus, - faCalendarCirclePlus, - faCalendarMinus, - faCalendarPlus, - faCode, - faGlobe, - faReact, - faSignalBars, - faTag, - faTs, -} from "@rivet-gg/icons"; -import { useNavigate, useSearch } from "@tanstack/react-router"; -import { useAtomValue, useSetAtom } from "jotai"; -import { useCallback, useMemo } from "react"; -import { - actorFiltersAtom, - actorFiltersCountAtom, - actorRegionsAtom, - actorTagsAtom, - actorsAtomsAtom, - actorsPaginationAtom, - actorsQueryAtom, - filteredActorsCountAtom, -} from "./actor-context"; -import { ActorStatus } from "./actor-status"; -import type { ActorStatus as ActorStatusType } from "./actor-status-indicator"; -import { ActorTag } from "./actor-tags"; -import { ActorsListRow } from "./actors-list-row"; -import { useActorsView } from "./actors-view-context-provider"; -import { CreateActorButton } from "./create-actor-button"; -import { GoToActorButton } from "./go-to-actor-button"; - -export function ActorsList() { - return ( - <> - -
-
-
- -
- - -
- -
-
-
-
- - Region - - - - -
-
ID
-
Tags
-
- - Created - - - - -
-
- - Destroyed - - - - -
-
-
- - -
- - - ); -} - -function LoadingIndicator() { - const state = useAtomValue(actorsQueryAtom); - if (state.isLoading) { - return ; - } - return null; -} - -function List() { - const actors = useAtomValue(actorsAtomsAtom); - return ( - <> - {actors.map((actor) => ( - - ))} - - ); -} - -function Pagination() { - const { hasNextPage, isFetchingNextPage, fetchNextPage } = - useAtomValue(actorsPaginationAtom); - - if (hasNextPage) { - return ( -
- -
- ); - } - - return ; -} - -function EmptyState() { - const count = useAtomValue(filteredActorsCountAtom); - const filtersCount = useAtomValue(actorFiltersCountAtom); - const setFilters = useSetAtom(actorFiltersAtom); - const { copy } = useActorsView(); - - return ( -
- {count === 0 ? ( - filtersCount === 0 ? ( -
- - - {copy.noActorsFound} - -
- {" "} - - Use one of the quick start guides to get - started. - -
- - - - - - -
-
-
- ) : ( - <> - - {copy.noActorsMatchFilter} - - - - ) - ) : ( - - {copy.noMoreActors} - - )} -
- ); -} - -const FILTER_DEFINITIONS = { - tags: { - type: "select", - label: "Tags", - icon: faTag, - options: TagsOptions, - operators: { - [FilterOp.EQUAL]: "is one of", - [FilterOp.NOT_EQUAL]: "is not one of", - }, - }, - createdAt: { - type: "date", - label: "Created", - icon: faCalendarCirclePlus, - }, - destroyedAt: { - type: "date", - label: "Destroyed", - icon: faCalendarCircleMinus, - }, - status: { - type: "select", - label: "Status", - icon: faSignalBars, - options: StatusOptions, - display: ({ value }) => { - if (value.length > 1) { - return {value.length} statuses; - } - return ( - - ); - }, - }, - region: { - type: "select", - label: "Region", - icon: faGlobe, - options: RegionOptions, - display: ({ value }) => { - if (value.length > 1) { - return {value.length} regions; - } - const region = useAtomValue(actorRegionsAtom).find( - (region) => region.id === value[0], - ); - return {region?.name}; - }, - operators: { - [FilterOp.EQUAL]: "is one of", - [FilterOp.NOT_EQUAL]: "is not one of", - }, - }, - devMode: { - type: "boolean", - label: "Show hidden actors", - icon: faCode, - }, -} satisfies FilterDefinitions; - -export const ActorsListFiltersSchema = createFiltersSchema(FILTER_DEFINITIONS); - -export const pickActorListFilters = createFiltersPicker(FILTER_DEFINITIONS); - -function Filters() { - const navigate = useNavigate(); - const filters = useSearch({ strict: false }); - - const onFiltersChange: OnFiltersChange = useCallback( - (fnOrValue) => { - if (typeof fnOrValue === "function") { - navigate({ - search: ({ actorId, tab, ...filters }) => ({ - actorId, - tab, - ...Object.fromEntries( - Object.entries(fnOrValue(filters)).filter( - ([, filter]) => filter.value.length > 0, - ), - ), - }), - }); - } else { - navigate({ - search: (value) => ({ - actorId: value.actorId, - tab: value.tab, - ...Object.fromEntries( - Object.entries(fnOrValue).filter( - ([, filter]) => filter.value.length > 0, - ), - ), - }), - }); - } - }, - [navigate], - ); - - const { copy } = useActorsView(); - - const filtersDefs = useMemo(() => { - return { - ...FILTER_DEFINITIONS, - devMode: { - ...FILTER_DEFINITIONS.devMode, - hidden: true, - label: copy.showHiddenActors, - }, - }; - }, [copy.showHiddenActors]); - - return ( - - ); -} - -function TagsOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - const tags = useAtomValue(actorTagsAtom); - - const values = filterValue.map((filter) => filter.split("=")); - - return ( - - {tags.map(({ key, value }) => { - const isSelected = values.some( - ([filterKey, filterValue]) => - filterKey === key && filterValue === value, - ); - return ( - { - if (isSelected) { - onSelect( - values - .filter( - ([filterKey, filterValue]) => - filterKey !== key || - filterValue !== value, - ) - .map((pair) => pair.join("=")), - { closeAfter: true }, - ); - return; - } - onSelect([...filterValue, `${key}=${value}`], { - closeAfter: true, - }); - }} - > - - - - {key}={value} - - - - ); - })} - - ); -} - -function StatusOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - return ( - - {["running", "starting", "crashed", "stopped"].map((key) => { - const isSelected = filterValue.some((val) => val === key); - return ( - { - if (isSelected) { - onSelect( - filterValue.filter( - (filterKey) => filterKey !== key, - ), - { closeAfter: true }, - ); - return; - } - - onSelect([...filterValue, key], { - closeAfter: true, - }); - }} - > - - - - ); - })} - - ); -} - -function RegionOptions({ onSelect, value: filterValue }: OptionsProviderProps) { - const regions = useAtomValue(actorRegionsAtom); - return ( - - {regions.map(({ id, name }) => { - const isSelected = filterValue.some((val) => val === id); - return ( - { - if (isSelected) { - onSelect( - filterValue.filter( - (filterKey) => filterKey !== id, - ), - { closeAfter: true }, - ); - return; - } - - onSelect([...filterValue, id], { - closeAfter: true, - }); - }} - > - - {name} - - ); - })} - - ); -} diff --git a/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx b/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx deleted file mode 100644 index d210653c7a..0000000000 --- a/frontend/packages/components/src/actors/actors-sidebar-toggle-button.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from "@rivet-gg/components"; -import { Icon, faSidebar } from "@rivet-gg/icons"; -import { useActorsLayout } from "./actors-layout-context"; - -export function ActorsSidebarToggleButton() { - const { setFolded, isFolded } = useActorsLayout(); - - if (!isFolded) { - return null; - } - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/actors-view-context-provider.tsx b/frontend/packages/components/src/actors/actors-view-context-provider.tsx deleted file mode 100644 index d7a063f1ae..0000000000 --- a/frontend/packages/components/src/actors/actors-view-context-provider.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { createContext, useContext } from "react"; - -const defaultValue = { - copy: { - createActor: "Create Actor", - createActorUsingForm: "Create Actor using simple form", - noActorsFound: "No Actors found", - selectActor: ( - <> - No Actor selected. -
{" "} - - Select an Actor from the list to view its details. - - - ), - goToActor: "Go to Actor", - showActorList: "Show Actor List", - showHiddenActors: "Show hidden Actors", - actorId: "Actor ID", - noActorsMatchFilter: "No Actors match the filters.", - noMoreActors: "No more Actors to load.", - - createActorModal: { - title: "Create Actor", - description: - "Choose a build to create an Actor from. Actor will be created using default settings.", - }, - - actorNotFound: "Actor not found", - actorNotFoundDescription: - "Change your filters to find the Actor you are looking for.", - - gettingStarted: { - title: "Getting Started with Actors", - description: - "Use a quick start guide to start deploying Actors to your environment.", - }, - }, - canCreate: true, -}; - -export const ActorsViewContext = - createContext(defaultValue); - -export const useActorsView = () => { - const context = useContext(ActorsViewContext); - if (!context) { - throw new Error( - "useActorsView must be used within a ActorsViewContextProvider", - ); - } - return context; -}; diff --git a/frontend/packages/components/src/actors/build-select.tsx b/frontend/packages/components/src/actors/build-select.tsx deleted file mode 100644 index ac5ed0eecf..0000000000 --- a/frontend/packages/components/src/actors/build-select.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Badge, Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { useMemo } from "react"; -import { actorBuildsAtom } from "./actor-context"; - -interface BuildSelectProps { - onValueChange: (value: string) => void; - value: string; - onlyCurrent?: boolean; -} - -export function BuildSelect({ - onValueChange, - value, - onlyCurrent, -}: BuildSelectProps) { - const data = useAtomValue(actorBuildsAtom); - - const builds = useMemo(() => { - let sorted = data.toSorted( - (a, b) => b.createdAt.valueOf() - a.createdAt.valueOf(), - ); - - if (onlyCurrent) { - sorted = sorted.filter((build) => build.tags.current); - } - - const findLatest = (name: string) => - sorted.find((build) => build.tags.name === name); - return sorted.map((build, index, array) => { - return { - label: ( -
-
-
- {build.tags.name || build.id.split("-")[0]} - - {findLatest(build.tags.name)?.id === - build.id ? ( - - Latest - - ) : null} -
-
- Created: {build.createdAt.toLocaleString()} -
-
-
- ), - value: build.id, - build, - }; - }); - }, [data, onlyCurrent]); - - return ( - - option.build.name.includes(search) || - option.build.tags.name.includes(search) - } - className="w-full h-14" - /> - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-input.tsx b/frontend/packages/components/src/actors/console/actor-console-input.tsx deleted file mode 100644 index 58689575ef..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-input.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Button, ScrollArea } from "@rivet-gg/components"; -import { useRef } from "react"; -import { useActorRpcs, useActorWorker } from "../worker/actor-worker-context"; -import { ActorConsoleMessage } from "./actor-console-message"; -import { ReplInput, type ReplInputRef, replaceCode } from "./repl-input"; - -export function ActorConsoleInput() { - const worker = useActorWorker(); - const rpcs = useActorRpcs(); - const ref = useRef(null); - - return ( -
- - - { - worker.run(code); - }} - /> - -
-
- {rpcs.map((rpc) => ( - - ))} -
-
-
-
- ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx b/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx deleted file mode 100644 index 11545a63f5..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-log-formatted.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { FormattedCode } from "../worker/actor-worker-schema"; - -export function ActorConsoleLogFormatted({ tokens }: FormattedCode) { - return ( - <> - {tokens.map((tokensLine, index) => ( - - {tokensLine.map((token, index) => ( - - {token.content} - - ))} - - ))} - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-log.tsx b/frontend/packages/components/src/actors/console/actor-console-log.tsx deleted file mode 100644 index 38b97eb0cf..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-log.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { memo } from "react"; -import type { ReplCommand } from "../worker/actor-worker-container"; -import { ActorConsoleLogFormatted } from "./actor-console-log-formatted"; -import { ActorConsoleMessage } from "./actor-console-message"; -import { ActorObjectInspector } from "./actor-inspector"; - -type ActorConsoleLogProps = ReplCommand & { - showTimestmaps: boolean; -}; - -export const ActorConsoleLog = memo((props: ActorConsoleLogProps) => { - return ( - <> - - {"formatted" in props && props.formatted ? ( - - ) : null} - - {"error" in props ? ( - - {props.error && - typeof props.error === "object" && - "toString" in props.error - ? props.error.toString() - : JSON.stringify(props.error)} - - ) : null} - {props.logs?.map((log, index) => ( - - {log.data?.map((element, key) => { - if (typeof element === "string") { - return ( - - {element} - - ); - } - return ( - - ); - })} - - ))} - {"result" in props ? ( - - {typeof props.result === "string" ? ( - props.result - ) : ( - - )} - - ) : null} - - ); -}); diff --git a/frontend/packages/components/src/actors/console/actor-console-logs.tsx b/frontend/packages/components/src/actors/console/actor-console-logs.tsx deleted file mode 100644 index bd3593a6ba..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-logs.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { ScrollArea } from "@rivet-gg/components"; -import { useLayoutEffect, useRef } from "react"; -import { useActorDetailsSettings } from "../actor-details-settings"; -import { useActorReplCommands } from "../worker/actor-worker-context"; -import { ActorConsoleLog } from "./actor-console-log"; - -export function ActorConsoleLogs() { - const isScrolledToBottom = useRef(true); - const ref = useRef(null); - const commands = useActorReplCommands(); - - const [settings] = useActorDetailsSettings(); - - // biome-ignore lint/correctness/useExhaustiveDependencies: we want to run this effect on every commands change - useLayoutEffect(() => { - if (ref.current && isScrolledToBottom.current) { - ref.current.scrollTop = ref.current.scrollHeight; - } - }, [commands]); - - return ( - { - if (ref.current) { - isScrolledToBottom.current = - ref.current.scrollTop + ref.current.clientHeight >= - ref.current.scrollHeight - 1; - } - }, - }} - className="w-full flex-1" - > - {commands.map((log) => ( - - ))} - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-console-message.tsx b/frontend/packages/components/src/actors/console/actor-console-message.tsx deleted file mode 100644 index eaba9b1e3d..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console-message.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import { - Icon, - faAngleLeft, - faAngleRight, - faExclamationCircle, - faSpinnerThird, - faWarning, -} from "@rivet-gg/icons"; -import { format } from "date-fns"; -import { type ReactNode, forwardRef } from "react"; - -interface ActorConsoleMessageProps { - variant: - | "input" - | "input-pending" - | "output" - | "error" - | "log" - | "warn" - | "info" - | "debug"; - timestamp?: Date; - className?: string; - children: ReactNode; -} - -export const ActorConsoleMessage = forwardRef< - HTMLDivElement, - ActorConsoleMessageProps ->(({ children, variant, timestamp, className, ...props }, ref) => { - return ( -
-
- -
-
- {timestamp - ? format(timestamp, "LLL dd HH:mm:ss").toUpperCase() - : null} -
-
- {children} -
-
- ); -}); - -export const ConsoleMessageVariantIcon = ({ - variant, - className, -}: { - variant: string; - className?: string; -}) => { - if (variant === "input") { - return ; - } - if (variant === "input-pending") { - return ( - - ); - } - if (variant === "output") { - return ; - } - if (variant === "error") { - return ( - - ); - } - if (variant === "warn") { - return ; - } - return ; -}; - -export const getConsoleMessageVariant = (variant: string) => - cn({ - "bg-red-950/30 border-red-800/40 text-red-400 z-10": - variant === "error", - "bg-yellow-500/10 border-yellow-800/40 text-yellow-200 z-10": - variant === "warn", - "bg-blue-950/30 border-blue-800/40 text-blue-400 z-10": - variant === "debug", - }); diff --git a/frontend/packages/components/src/actors/console/actor-console.tsx b/frontend/packages/components/src/actors/console/actor-console.tsx deleted file mode 100644 index 681cbbf10d..0000000000 --- a/frontend/packages/components/src/actors/console/actor-console.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Button } from "@rivet-gg/components"; -import { Icon, faChevronDown } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import { useState } from "react"; -import { useActorWorkerStatus } from "../worker/actor-worker-context"; -import { ActorWorkerStatus } from "../worker/actor-worker-status"; -import { ActorConsoleInput } from "./actor-console-input"; -import { ActorConsoleLogs } from "./actor-console-logs"; - -export function ActorConsole() { - const [isOpen, setOpen] = useState(false); - - const status = useActorWorkerStatus(); - - const isBlocked = status.type !== "ready"; - - return ( - - - - {isOpen && !isBlocked ? ( - - - - - ) : null} - - - ); -} diff --git a/frontend/packages/components/src/actors/console/actor-inspector.tsx b/frontend/packages/components/src/actors/console/actor-inspector.tsx deleted file mode 100644 index 87949e7a13..0000000000 --- a/frontend/packages/components/src/actors/console/actor-inspector.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { cn } from "@rivet-gg/components"; -import type { ComponentProps } from "react"; -import { Inspector, type ObjectInspector, chromeDark } from "react-inspector"; - -const INSPECTOR_THEME = { - ...chromeDark, - BASE_BACKGROUND_COLOR: "transparent", -}; - -export function ActorObjectInspector( - props: ComponentProps, -) { - return ( -
- -
- ); -} diff --git a/frontend/packages/components/src/actors/console/repl-input.tsx b/frontend/packages/components/src/actors/console/repl-input.tsx deleted file mode 100644 index fc7ef1e372..0000000000 --- a/frontend/packages/components/src/actors/console/repl-input.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - CodeMirror, - type CodeMirrorRef, - type CompletionContext, - EditorView, - External, - defaultKeymap, - javascript, - javascriptLanguage, - keymap, -} from "@rivet-gg/components/code-mirror"; -import { forwardRef } from "react"; - -export const replaceCode = (editor: EditorView, code: string) => { - return editor.dispatch({ - changes: { - from: 0, - to: editor.state.doc.length, - insert: code, - }, - selection: { anchor: code.length }, - scrollIntoView: true, - annotations: [External.of(true)], - }); -}; - -const deleteBgTheme = EditorView.theme({ - ".cm-content": { padding: 0 }, -}); - -export type ReplInputRef = CodeMirrorRef; - -interface ReplInputProps { - className: string; - rpcs: string[]; - onRun: (code: string) => void; -} - -export const ReplInput = forwardRef( - ({ rpcs, onRun, className }, ref) => { - const rivetKeymap = keymap.of([ - { - key: "Enter", - run: (editor) => { - onRun(editor?.state.doc.toString()); - editor.dispatch({ - changes: { - from: 0, - to: editor.state.doc.length, - insert: "", - }, - annotations: [External.of(true)], - }); - return true; - }, - }, - ...defaultKeymap, - ]); - - const replAutocomplete = javascriptLanguage.data.of({ - autocomplete: (context: CompletionContext) => { - const word = context.matchBefore(/^actor\.\w*/); - if (!word || (word?.from === word?.to && !context.explicit)) - return null; - return { - from: word.from, - to: word.to, - boost: 99, - options: rpcs.map((rpc) => ({ - label: `actor.${rpc}(/* args */)`, - apply: `actor.${rpc}(`, - validFor: /^actor\.\w*$/, - info: `Call "${rpc}" RPC on Actor`, - })), - }; - }, - }); - - return ( - - ); - }, -); diff --git a/frontend/packages/components/src/actors/create-actor-button.tsx b/frontend/packages/components/src/actors/create-actor-button.tsx deleted file mode 100644 index 3748de59bb..0000000000 --- a/frontend/packages/components/src/actors/create-actor-button.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Button, type ButtonProps, WithTooltip } from "@rivet-gg/components"; -import { Icon, faPlus } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; -import { useAtomValue } from "jotai"; -import { - actorBuildsCountAtom, - actorManagerEndpointAtom, -} from "./actor-context"; -import { useActorsView } from "./actors-view-context-provider"; - -export function CreateActorButton(props: ButtonProps) { - const navigate = useNavigate(); - const builds = useAtomValue(actorBuildsCountAtom); - const endpoint = useAtomValue(actorManagerEndpointAtom); - - const { copy, canCreate: contextAllowActorsCreation } = useActorsView(); - - const canCreate = builds > 0 && contextAllowActorsCreation && endpoint; - - if (!contextAllowActorsCreation) { - return null; - } - - const content = ( -
- -
- ); - - if (canCreate) { - return content; - } - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/current-environment-version-title.tsx b/frontend/packages/components/src/actors/current-environment-version-title.tsx deleted file mode 100644 index e38d15afec..0000000000 --- a/frontend/packages/components/src/actors/current-environment-version-title.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useSuspenseQuery } from "@tanstack/react-query"; -import { - projectEnvironmentQueryOptions, - projectVersionQueryOptions, -} from "../queries"; -import { EnvironmentVersionTitle } from "./environment-version-title"; - -interface CurrentEnvironmentVersionTitleProps { - environmentId: string; - projectId: string; -} - -export function CurrentEnvironmentVersionTitle({ - environmentId, - projectId, -}: CurrentEnvironmentVersionTitleProps) { - const { - data: { namespace: environment }, - } = useSuspenseQuery( - projectEnvironmentQueryOptions({ projectId, environmentId }), - ); - - const { data: version } = useSuspenseQuery( - projectVersionQueryOptions({ - projectId, - versionId: environment.versionId, - }), - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx b/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx deleted file mode 100644 index 9dc8b0745f..0000000000 --- a/frontend/packages/components/src/actors/dialogs/create-actor-dialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useAtomValue } from "jotai"; -import { - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "../../ui/dialog"; -import { Flex } from "../../ui/flex"; -import { createActorAtom } from "../actor-context"; -import { useActorsView } from "../actors-view-context-provider"; -import * as ActorCreateForm from "../form/actor-create-form"; -import type { DialogContentProps } from "../hooks"; - -interface ContentProps extends DialogContentProps {} - -export default function CreateActorDialog({ onClose }: ContentProps) { - const { endpoint, create } = useAtomValue(createActorAtom); - - const { copy } = useActorsView(); - - return ( - <> - { - if (!endpoint) { - throw new Error("No endpoint"); - } - await create({ - endpoint, - id: values.buildId, - tags: Object.fromEntries( - values.tags.map((tag) => [tag.key, tag.value]), - ), - params: values.parameters - ? JSON.parse(values.parameters) - : undefined, - region: values.regionId, - }); - onClose?.(); - }} - defaultValues={{ buildId: "", regionId: "" }} - > - - {copy.createActorModal.title} - - {copy.createActorModal.description} - - - - - - - {/* */} - - - - Create - - - - - ); -} diff --git a/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx b/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx deleted file mode 100644 index 3baf32d185..0000000000 --- a/frontend/packages/components/src/actors/dialogs/go-to-actor-dialog.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Button } from "../../ui/button"; -import { DialogFooter, DialogHeader, DialogTitle } from "../../ui/dialog"; -import { useActorsView } from "../actors-view-context-provider"; -import * as GoToActorForm from "../form/go-to-actor-form"; -import type { DialogContentProps } from "../hooks"; - -interface ContentProps extends DialogContentProps { - onSubmit?: (actorId: string) => void; -} - -export default function GoToActorDialogContent({ - onClose, - onSubmit, -}: ContentProps) { - const { copy } = useActorsView(); - return ( - { - onSubmit?.(actorId); - }} - > - - {copy.goToActor} - - - - - Go - - - ); -} diff --git a/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx b/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx deleted file mode 100644 index 7df263034b..0000000000 --- a/frontend/packages/components/src/actors/dynamic-servers-feature-card.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { - Card, - CardContent, - CardHeader, - CardTitle, - Link, - Text, -} from "@rivet-gg/components"; -import { Link as RouterLink } from "@tanstack/react-router"; - -export function DynamicServersFeatureCard() { - return ( - - - Legacy Lobbies and Environments - - - - Dynamic servers and builds are the new way to manage your - project servers. However, if you're looking for lobbies and - namespaces, you can switch back to the old interface in the{" "} - - - User Settings - - - . - - - - ); -} diff --git a/frontend/packages/components/src/actors/environment-select.tsx b/frontend/packages/components/src/actors/environment-select.tsx deleted file mode 100644 index 2261946dbc..0000000000 --- a/frontend/packages/components/src/actors/environment-select.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { projectEnvironmentsQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, useCallback } from "react"; - -interface EnvironmentSelectProps extends ComponentProps { - projectId: string; - showCreateEnvironment?: boolean; - onCreateClick?: () => void; - variant?: ComponentProps["variant"]; -} - -export function EnvironmentSelect({ - showCreateEnvironment, - onCreateClick, - onValueChange, - projectId, - variant, - ...props -}: EnvironmentSelectProps) { - const { data } = useSuspenseQuery( - projectEnvironmentsQueryOptions(projectId), - ); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/environment-version-title.tsx b/frontend/packages/components/src/actors/environment-version-title.tsx deleted file mode 100644 index 0777d81cda..0000000000 --- a/frontend/packages/components/src/actors/environment-version-title.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Badge, Flex } from "@rivet-gg/components"; - -interface EnvironmentVersionTitleProps { - environment: string; - version: string; -} - -export function EnvironmentVersionTitle({ - environment, - version, -}: EnvironmentVersionTitleProps) { - return ( - - {environment} - {version} - - ); -} diff --git a/frontend/packages/components/src/actors/form/actor-create-form.tsx b/frontend/packages/components/src/actors/form/actor-create-form.tsx deleted file mode 100644 index bf92b99b46..0000000000 --- a/frontend/packages/components/src/actors/form/actor-create-form.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Label, - createSchemaForm, -} from "@rivet-gg/components"; -import { JsonCode } from "@rivet-gg/components/code-mirror"; -import { type UseFormReturn, useFormContext } from "react-hook-form"; -import z from "zod"; - -import { useAtomValue, useSetAtom } from "jotai"; -import { - actorCustomTagKeys, - actorCustomTagValues, - actorTagKeysAtom, - actorTagValuesAtom, -} from "../actor-context"; -import { BuildSelect } from "../build-select"; -import { RegionSelect } from "../region-select"; -import { - Tags as TagsInput, - formSchema as tagsFormSchema, -} from "./build-tags-form"; - -const jsonValid = z.custom((value) => { - try { - JSON.parse(value); - return true; - } catch { - return false; - } -}); - -export const formSchema = z.object({ - buildId: z.string().nonempty("Build is required"), - regionId: z.string(), - parameters: jsonValid.optional(), - tags: tagsFormSchema.shape.tags, -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const Build = () => { - const { control } = useFormContext(); - return ( - ( - - Build - - - - - - )} - /> - ); -}; - -export const Region = () => { - const { control } = useFormContext(); - - return ( - ( - - Region - - - - - - )} - /> - ); -}; - -export const Parameters = () => { - const { control } = useFormContext(); - return ( - ( - - Parameters - - - - - - )} - /> - ); -}; - -export const Tags = () => { - const setValues = useSetAtom(actorCustomTagValues); - const setKeys = useSetAtom(actorCustomTagKeys); - - const keys = useAtomValue(actorTagKeysAtom); - const values = useAtomValue(actorTagValuesAtom); - - return ( -
- - ({ - label: key, - value: key, - }))} - values={values.map((value) => ({ - label: value, - value: value, - }))} - onCreateKeyOption={(value) => { - setKeys((old) => - Array.from(new Set([...old, value]).values()), - ); - }} - onCreateValueOption={(value) => { - setValues((old) => - Array.from(new Set([...old, value]).values()), - ); - }} - /> -
- ); -}; diff --git a/frontend/packages/components/src/actors/form/build-tags-form.tsx b/frontend/packages/components/src/actors/form/build-tags-form.tsx deleted file mode 100644 index b431baf7cd..0000000000 --- a/frontend/packages/components/src/actors/form/build-tags-form.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { Icon, faTrash } from "@rivet-gg/icons"; -import { - type UseFormReturn, - useFieldArray, - useFormContext, -} from "react-hook-form"; -import z from "zod"; -import { createSchemaForm } from "../../lib/create-schema-form"; -import { Button } from "../../ui/button"; -import { Combobox, type ComboboxOption as Option } from "../../ui/combobox"; -import { - FormControl, - FormFieldContext, - FormItem, - FormLabel, - FormMessage, -} from "../../ui/form"; -import { Text } from "../../ui/typography"; - -export const formSchema = z.object({ - tags: z - .array( - z.object({ - key: z.string().min(1), - value: z.string(), - }), - ) - .superRefine((tags, ctx) => { - const tagsSet = new Set(); - for (const [idx, tag] of [...tags].reverse().entries()) { - if (tagsSet.has(tag.key)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: [idx, "key"], - message: "Tag keys must be unique", - }); - } - tagsSet.add(tag.key); - } - }), -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const Tags = ({ - onCreateKeyOption, - onCreateValueOption, - keys, - values, -}: { - onCreateKeyOption: (option: string) => void; - onCreateValueOption: (option: string) => void; - keys: Option[]; - values: Option[]; -}) => { - const { control, setValue, watch } = useFormContext(); - const { fields, append, remove } = useFieldArray({ - name: "tags", - control, - }); - - return ( - <> - {fields.length === 0 ? ( - - There's no tags added. - - ) : null} - {fields.map((field, index) => ( -
- - - Key - - { - setValue(`tags.${index}.key`, value, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); - }} - allowCreate - onCreateOption={onCreateKeyOption} - /> - - - - - - - - Value - - { - setValue(`tags.${index}.value`, value, { - shouldDirty: true, - shouldTouch: true, - shouldValidate: true, - }); - }} - allowCreate - onCreateOption={onCreateValueOption} - /> - - - - - -
- ))} - - - ); -}; diff --git a/frontend/packages/components/src/actors/form/go-to-actor-form.tsx b/frontend/packages/components/src/actors/form/go-to-actor-form.tsx deleted file mode 100644 index 4b7acdf0d2..0000000000 --- a/frontend/packages/components/src/actors/form/go-to-actor-form.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { type UseFormReturn, useFormContext } from "react-hook-form"; -import z from "zod"; -import { createSchemaForm } from "../../lib/create-schema-form"; -import { - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "../../ui/form"; -import { Input } from "../../ui/input"; -import { useActorsView } from "../actors-view-context-provider"; - -export const formSchema = z.object({ - actorId: z.string().nonempty("Actor ID is required").uuid(), -}); - -export type FormValues = z.infer; -export type SubmitHandler = ( - values: FormValues, - form: UseFormReturn, -) => Promise; - -const { Form, Submit } = createSchemaForm(formSchema); -export { Form, Submit }; - -export const ActorId = () => { - const { control } = useFormContext(); - const { copy } = useActorsView(); - return ( - ( - - {copy.actorId} - - - - - - )} - /> - ); -}; diff --git a/frontend/packages/components/src/actors/get-started.tsx b/frontend/packages/components/src/actors/get-started.tsx deleted file mode 100644 index 8039cb19f9..0000000000 --- a/frontend/packages/components/src/actors/get-started.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Icon, faActors, faFunction, faServer } from "@rivet-gg/icons"; -import { motion } from "framer-motion"; -import type { ComponentProps } from "react"; -import { DocsSheet } from "../docs-sheet"; -import { cn } from "../lib/utils"; -import { Button } from "../ui/button"; - -export function ActorsResources() { - return ( - <> -
- - - -
- - ); -} - -const linkVariants = { - hidden: { - opacity: 0, - }, - show: { - opacity: 1, - }, -}; - -interface ExampleLinkProps { - title: string; - description?: string; - icon: ComponentProps["icon"]; - href: string; - size?: "sm" | "md" | "lg"; -} - -function ExampleLink({ - title, - description, - icon, - href, - size = "lg", -}: ExampleLinkProps) { - return ( - - - - ); -} diff --git a/frontend/packages/components/src/actors/getting-started.tsx b/frontend/packages/components/src/actors/getting-started.tsx deleted file mode 100644 index e64f8ac502..0000000000 --- a/frontend/packages/components/src/actors/getting-started.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Icon, faActors } from "@rivet-gg/icons"; -import { useActorsView } from "./actors-view-context-provider"; -import { ActorsResources } from "./get-started"; - -export function GettingStarted() { - const { copy } = useActorsView(); - return ( -
-
- -

- {copy.gettingStarted.title} -

-

- {copy.gettingStarted.description} -

-
- -
- ); -} diff --git a/frontend/packages/components/src/actors/go-to-actor-button.tsx b/frontend/packages/components/src/actors/go-to-actor-button.tsx deleted file mode 100644 index b38d67f676..0000000000 --- a/frontend/packages/components/src/actors/go-to-actor-button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Button, type ButtonProps } from "@rivet-gg/components"; -import { Icon, faMagnifyingGlass } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; -import { useActorsView } from "./actors-view-context-provider"; - -export function GoToActorButton(props: ButtonProps) { - const navigate = useNavigate(); - const { copy } = useActorsView(); - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/group-project-select.tsx b/frontend/packages/components/src/actors/group-project-select.tsx deleted file mode 100644 index d1f7fa6c86..0000000000 --- a/frontend/packages/components/src/actors/group-project-select.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { groupProjectsQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, useCallback } from "react"; - -interface GroupProjectSelectProps extends ComponentProps { - groupId: string; - showCreateProject?: boolean; - onCreateClick?: () => void; - variant?: ComponentProps["variant"]; -} - -export function GroupProjectSelect({ - groupId, - showCreateProject, - onCreateClick, - onValueChange, - variant, - ...props -}: GroupProjectSelectProps) { - const { data } = useSuspenseQuery(groupProjectsQueryOptions(groupId)); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/hooks/index.ts b/frontend/packages/components/src/actors/hooks/index.ts deleted file mode 100644 index 14c4d0400f..0000000000 --- a/frontend/packages/components/src/actors/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./use-dialog"; diff --git a/frontend/packages/components/src/actors/hooks/use-dialog.tsx b/frontend/packages/components/src/actors/hooks/use-dialog.tsx deleted file mode 100644 index 77373da6c4..0000000000 --- a/frontend/packages/components/src/actors/hooks/use-dialog.tsx +++ /dev/null @@ -1,159 +0,0 @@ -"use client"; -import { - type ComponentProps, - type ComponentType, - lazy, - useCallback, - useMemo, - useState, -} from "react"; -import { Dialog, DialogContent, type DialogProps } from "../../ui/dialog"; - -export interface DialogContentProps { - onClose?: () => void; -} - -interface DialogConfig { - autoFocus?: boolean; -} - -export const createDialogHook = < - // biome-ignore lint/suspicious/noExplicitAny: we don't know the type of the component, so we use any - Component extends Promise<{ default: ComponentType }>, ->( - component: Component, - opts: DialogConfig = {}, -) => { - const DialogImpl = ({ - dialogProps, - ...props - }: ComponentProps["default"]> & { - dialogProps?: DialogProps; - }) => { - // biome-ignore lint/correctness/useExhaustiveDependencies: component here is a static value, won't change over time - const Content = useMemo(() => lazy(() => component), []); - - return ( - - { - if (opts.autoFocus === false) { - return e.preventDefault(); - } - }} - > - dialogProps?.onOpenChange?.(false)} - /> - - - ); - }; - - const useHook = (props: ComponentProps["default"]>) => { - const [isOpen, setIsOpen] = useState(() => false); - - const close = useCallback(() => { - setIsOpen(false); - }, []); - - const open = useCallback(() => { - setIsOpen(true); - }, []); - - const handleOpenChange = useCallback((open: boolean) => { - setIsOpen(open); - }, []); - - return { - open, - close, - dialog: ( - - ), - }; - }; - - useHook.Dialog = DialogImpl; - - return useHook; -}; - -export const createDataDialogHook = < - const DataPropKeys extends string[], - // biome-ignore lint/suspicious/noExplicitAny: we don't know the type of the component, so we use any - Component extends Promise<{ default: ComponentType }>, ->( - _: DataPropKeys, - component: Component, - opts: DialogConfig = {}, -) => { - return ( - props: Omit< - ComponentProps["default"]>, - DataPropKeys[number] - >, - ) => { - const [isOpen, setIsOpen] = useState(false); - const [data, setData] = - useState< - Pick< - ComponentProps["default"]>, - DataPropKeys[number] - > - >(); - - const close = useCallback(() => { - setIsOpen(false); - }, []); - - const open = useCallback( - ( - data: Pick< - ComponentProps["default"]>, - DataPropKeys[number] - >, - ) => { - setIsOpen(true); - setData(data); - }, - [], - ); - - // biome-ignore lint/correctness/useExhaustiveDependencies: component here is a static value, won't change over time - const Content = useMemo(() => lazy(() => component), []); - - return { - open, - dialog: ( - - { - if (opts.autoFocus === false) { - return e.preventDefault(); - } - }} - > - - - - ), - }; - }; -}; - -export function useDialog() {} - -useDialog.GoToActor = createDialogHook(import("../dialogs/go-to-actor-dialog")); - -useDialog.Feedback = createDialogHook(import("../../dialogs/feedback-dialog")); -useDialog.CreateActor = createDialogHook( - import("../dialogs/create-actor-dialog"), -); diff --git a/frontend/packages/components/src/actors/index.tsx b/frontend/packages/components/src/actors/index.tsx deleted file mode 100644 index dd5baf45d2..0000000000 --- a/frontend/packages/components/src/actors/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export * from "./getting-started"; -export * from "./actors-list-preview"; -export * from "./actor-tags"; -export * from "./actor-context"; -export * from "./actors-actor-details"; -export * from "./hooks/index"; -export { getActorStatus } from "./actor-status-indicator"; -export * from "./actors-layout"; -export * from "./actors-layout-context"; -export * from "./console/actor-console-message"; -export * from "./actor-region"; -export * from "./console/actor-inspector"; -export * from "./actor-status-indicator"; -export * from "./actor-status-label"; -export * from "./actors-view-context-provider"; -export * from "./actor-not-found"; -export { ActorsListFiltersSchema, pickActorListFilters } from "./actors-list"; diff --git a/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx b/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx deleted file mode 100644 index c6112dea98..0000000000 --- a/frontend/packages/components/src/actors/matchmaker-lobby-config-settings-card.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as MatchmakerLobbyConfigForm from "@/domains/project/forms/matchmaker-lobby-config-form"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, - Flex, -} from "@rivet-gg/components"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { useMatchmakerLobbyConfigFormHandler } from "../hooks/use-matchmaker-lobby-config-form-handler"; -import { projectEnvironmentQueryOptions } from "../queries"; - -interface MatchMakerLobbyConfigSettingsCardProps { - projectId: string; - environmentId: string; -} - -export function MatchMakerLobbyConfigSettingsCard({ - environmentId, - projectId, -}: MatchMakerLobbyConfigSettingsCardProps) { - const { data } = useSuspenseQuery( - projectEnvironmentQueryOptions({ projectId, environmentId }), - ); - - const handleSubmit = useMatchmakerLobbyConfigFormHandler({ - environmentId, - projectId, - }); - - return ( - - - - Config - - - - - - - - - - Save - - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-builds-table-actions.tsx b/frontend/packages/components/src/actors/project-builds-table-actions.tsx deleted file mode 100644 index 47dcea2344..0000000000 --- a/frontend/packages/components/src/actors/project-builds-table-actions.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; -import { useNavigate } from "@tanstack/react-router"; - -interface ProjectBuildsTableActionsProps { - buildId: string; -} - -export function ProjectBuildsTableActions({ - buildId, -}: ProjectBuildsTableActionsProps) { - const navigate = useNavigate(); - return ( - - - - - - { - navigate({ - to: ".", - search: { modal: "edit-tags", buildId }, - }); - }} - > - Edit tags - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-environments-table-actions.tsx b/frontend/packages/components/src/actors/project-environments-table-actions.tsx deleted file mode 100644 index b6722a6274..0000000000 --- a/frontend/packages/components/src/actors/project-environments-table-actions.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; - -export function ProjectEnvironmentsTableActions() { - return ( - - - - - - Manage - - - ); -} diff --git a/frontend/packages/components/src/actors/project-logo-settings-card.tsx b/frontend/packages/components/src/actors/project-logo-settings-card.tsx deleted file mode 100644 index e4ede8afde..0000000000 --- a/frontend/packages/components/src/actors/project-logo-settings-card.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as GroupImageForm from "@/domains/project/forms/project-logo-form"; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "@rivet-gg/components"; -import { useProjectLogoUploadMutation } from "../queries"; - -interface ProjectLogoSettingsCardProps { - projectId: string; -} - -export function ProjectLogoSettingsCard({ - projectId, -}: ProjectLogoSettingsCardProps) { - const { mutateAsync } = useProjectLogoUploadMutation(projectId); - return ( - { - try { - await mutateAsync({ file: values.logo }); - } catch { - form.setError("logo", { - type: "manual", - message: "An error occurred while uploading the image", - }); - } - }} - defaultValues={{ logo: undefined }} - > - - - Project Logo - - - - - - Save - - - - ); -} diff --git a/frontend/packages/components/src/actors/project-select.tsx b/frontend/packages/components/src/actors/project-select.tsx deleted file mode 100644 index 1438de3313..0000000000 --- a/frontend/packages/components/src/actors/project-select.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { GroupAvatar } from "@/domains/group/components/group-avatar"; -import { projectsByGroupQueryOptions } from "@/domains/project/queries"; -import { - Flex, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectSeparator, - SelectTrigger, - SelectValue, -} from "@rivet-gg/components"; -import { Icon, faCirclePlus } from "@rivet-gg/icons"; -import { useSuspenseQuery } from "@tanstack/react-query"; -import { type ComponentProps, Fragment, useCallback } from "react"; - -interface ProjectSelectProps extends ComponentProps { - showCreateProject?: boolean; - onCreateClick?: () => void; -} - -export function ProjectSelect({ - showCreateProject, - onCreateClick, - onValueChange, - ...props -}: ProjectSelectProps) { - const { data } = useSuspenseQuery(projectsByGroupQueryOptions()); - - const handleValueChange = useCallback( - (value: string) => { - if (value === "create") { - onCreateClick?.(); - return; - } - onValueChange?.(value); - }, - [onCreateClick, onValueChange], - ); - - return ( - - ); -} diff --git a/frontend/packages/components/src/actors/project-table-actions.tsx b/frontend/packages/components/src/actors/project-table-actions.tsx deleted file mode 100644 index bd462c5d44..0000000000 --- a/frontend/packages/components/src/actors/project-table-actions.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@rivet-gg/components"; -import { Icon, faEllipsisH } from "@rivet-gg/icons"; - -export function ProjectTableActions() { - return ( - - - - - - Manage - - - ); -} diff --git a/frontend/packages/components/src/actors/project-tile.tsx b/frontend/packages/components/src/actors/project-tile.tsx deleted file mode 100644 index 0938f22eaa..0000000000 --- a/frontend/packages/components/src/actors/project-tile.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { Rivet } from "@rivet-gg/api"; -import { AssetImage, Flex, Text } from "@rivet-gg/components"; -import { BillingPlanBadge } from "./billing/billing-plan-badge"; - -interface ProjectTileProps - extends Pick< - Rivet.game.GameSummary, - "gameId" | "displayName" | "logoUrl" - > {} - -export function ProjectTile({ - gameId: projectId, - displayName, - logoUrl, -}: ProjectTileProps) { - return ( - -
- -
- {displayName} - -
- ); -} diff --git a/frontend/packages/components/src/actors/region-select.tsx b/frontend/packages/components/src/actors/region-select.tsx deleted file mode 100644 index d88d187105..0000000000 --- a/frontend/packages/components/src/actors/region-select.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Combobox } from "@rivet-gg/components"; -import { useAtomValue } from "jotai"; -import { actorRegionsAtom } from "./actor-context"; -import { ActorRegion } from "./actor-region"; - -interface RegionSelectProps { - onValueChange: (value: string) => void; - value: string; -} - -export function RegionSelect({ onValueChange, value }: RegionSelectProps) { - const data = useAtomValue(actorRegionsAtom); - - const regions = [ - { - label: "Automatic (Recommended)", - value: "", - region: { id: "", name: "Automatic (Recommended)" }, - }, - ...data.map((region) => { - return { - label: , - value: region.id, - region, - }; - }), - ]; - - return ( - { - const search = searchMixed.toLowerCase(); - return ( - option.region.id.includes(search) || - option.region.name.includes(search) - ); - }} - className="w-full" - /> - ); -} diff --git a/frontend/packages/components/src/actors/worker/actor-repl.worker.ts b/frontend/packages/components/src/actors/worker/actor-repl.worker.ts deleted file mode 100644 index 98753fabf2..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-repl.worker.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { - type InspectData, - type ToClient, - ToClientSchema, - type ToServer, -} from "actor-core/inspector/protocol/actor"; -import type { Request, ResponseOk } from "actor-core/protocol/http"; -import { fromJs } from "esast-util-from-js"; -import { toJs } from "estree-util-to-js"; -import { - createHighlighterCore, - createOnigurumaEngine, - type HighlighterCore, -} from "shiki"; -import { endWithSlash } from "../../lib/utils"; -import { - MessageSchema, - type ReplErrorCode, - type Response, - ResponseSchema, -} from "./actor-worker-schema"; - -class ReplError extends Error { - constructor( - public readonly code: ReplErrorCode, - message: string, - ) { - super(message); - } - - static unsupported() { - return new ReplError("unsupported", "Actor unsupported"); - } -} - -export let highlighter: HighlighterCore | undefined; - -async function formatCode(code: string) { - highlighter ??= await createHighlighterCore({ - themes: [import("shiki/themes/github-dark-default.mjs")], - langs: [import("@shikijs/langs/typescript")], - engine: createOnigurumaEngine(import("shiki/wasm")), - }); - - return highlighter.codeToTokens(code, { - lang: "typescript", - theme: "github-dark-default", - }); -} - -const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -async function evaluateCode(code: string, args: Record) { - const argsString = Object.keys(args); - const argValues = Object.values(args); - - let jsCode: ReturnType; - try { - const program = fromJs(code, { - module: true, - allowAwaitOutsideFunction: true, - allowReturnOutsideFunction: true, - }); - - const lastStatement = program.body[program.body.length - 1]; - if (lastStatement.type === "ExpressionStatement") { - program.body[program.body.length - 1] = { - type: "ReturnStatement", - argument: lastStatement.expression, - }; - } - - jsCode = toJs(program); - } catch (e) { - throw new ReplError("syntax", "Syntax error"); - } - - return new Function( - "window", - ...argsString, - `"use strict"; - return (async () => { - ${jsCode.value} - })() - `, - )({}, ...argValues); -} - -const createConsole = (id: string) => { - return new Proxy( - { ...console }, - { - get(target, prop) { - return (...args: unknown[]) => { - respond({ - type: "log", - id, - data: { - method: prop as "log" | "warn" | "error", - data: args, - timestamp: new Date().toISOString(), - }, - }); - return Reflect.get(target, prop)(...args); - }; - }, - }, - ); -}; - -let init: null | ({ ws: WebSocket; url: URL } & InspectData) = null; - -async function connect(endpoint: string, opts?: { token?: string }) { - const url = new URL("inspect", endWithSlash(endpoint)); - - if (opts?.token) { - url.searchParams.set("token", opts.token); - } - - const ws = new WebSocket(url); - - await waitForOpen(ws); - - ws.send( - JSON.stringify({ - type: "info", - } satisfies ToServer), - ); - - const { type: _, ...info } = await waitForMessage(ws, "info"); - init = { ...info, ws, url: new URL(endpoint) }; - - ws.addEventListener("message", (event) => { - try { - const data = ToClientSchema.parse(JSON.parse(event.data)); - - if (data.type === "info") { - return respond({ - type: "inspect", - data: { - ...data, - }, - }); - } - if (data.type === "error") { - return respond({ - type: "error", - data: data.message, - }); - } - } catch (error) { - console.warn("Malformed message", event.data, error); - return; - } - }); - - ws.addEventListener("close", () => { - respond({ - type: "lost-connection", - }); - setTimeout(() => { - connect(endpoint, opts); - }, 500); - }); - - respond({ - type: "ready", - data: { - ...info, - }, - }); -} - -addEventListener("message", async (event) => { - const { success, error, data } = MessageSchema.safeParse(event.data); - - if (!success) { - console.error("Malformed message", event.data, error); - return; - } - - if (data.type === "init") { - if (init) { - respond({ - type: "error", - data: new Error("Actor already initialized"), - }); - return; - } - - try { - await Promise.race([ - connect(data.endpoint, data.token ? { token: data.token } : {}), - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); - - return; - } catch (e) { - return respond({ - type: "error", - data: e, - }); - } - } - - if (data.type === "code") { - const actor = init; - if (!actor) { - respond({ - type: "error", - data: new Error("Actor not initialized"), - }); - return; - } - - try { - const formatted = await formatCode(data.data); - respond({ - type: "formatted", - id: data.id, - data: formatted, - }); - - const createRpc = - (rpc: string) => - async (...args: unknown[]) => { - const url = new URL( - `rpc/${rpc}`, - endWithSlash(actor.url.href), - ); - const response = await fetch(url, { - method: "POST", - body: JSON.stringify({ - a: args, - } satisfies Request), - }); - - if (!response.ok) { - throw new Error("RPC failed"); - } - - const data = (await response.json()) as ResponseOk; - return data.o; - }; - - const exposedActor = Object.fromEntries( - init?.rpcs.map((rpc) => [rpc, createRpc(rpc)]) ?? [], - ); - - const evaluated = await evaluateCode(data.data, { - console: createConsole(data.id), - wait, - actor: exposedActor, - }); - return respond({ - type: "result", - id: data.id, - data: evaluated, - }); - } catch (e) { - return respond({ - type: "error", - id: data.id, - data: e, - }); - } - } - - if (data.type === "set-state") { - const actor = init; - if (!actor) { - respond({ - type: "error", - data: new Error("Actor not initialized"), - }); - return; - } - - try { - const state = JSON.parse(data.data); - actor.ws.send( - JSON.stringify({ - type: "setState", - state, - } satisfies ToServer), - ); - } catch (e) { - return respond({ - type: "error", - data: e, - }); - } - } -}); - -function respond(msg: Response) { - return postMessage(ResponseSchema.parse(msg)); -} - -function waitForOpen(ws: WebSocket) { - const { promise, resolve, reject } = Promise.withResolvers(); - ws.addEventListener("open", () => { - resolve(undefined); - }); - ws.addEventListener("error", (event) => { - reject(); - }); - ws.addEventListener("close", (event) => { - reject(); - }); - - return Promise.race([ - promise, - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); -} - -function waitForMessage( - ws: WebSocket, - type: T, -): Promise> { - const { promise, resolve, reject } = - Promise.withResolvers>(); - - function onMessage(event: MessageEvent) { - try { - const data = ToClientSchema.parse(JSON.parse(event.data)); - - if (data.type === type) { - resolve(data as Extract); - ws.removeEventListener("message", onMessage); - } - } catch (e) { - console.error(e); - } - } - - ws.addEventListener("message", onMessage); - ws.addEventListener("error", (event) => { - ws.removeEventListener("message", onMessage); - reject(); - }); - - return Promise.race([ - promise, - wait(5000).then(() => { - throw new Error("Timeout"); - }), - ]); -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-container.ts b/frontend/packages/components/src/actors/worker/actor-worker-container.ts deleted file mode 100644 index 7352e8e6bc..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-container.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { CancelledError } from "@tanstack/react-query"; -import { toast } from "sonner"; -import { ls } from "../../lib/utils"; -import ActorWorker from "./actor-repl.worker?worker"; -import { - type CodeMessage, - type FormattedCode, - type InitMessage, - type InspectData, - type Log, - ResponseSchema, - type SetStateMessage, -} from "./actor-worker-schema"; - -export type ReplCommand = { - logs: Log[]; - code: string; - key: string; - inputTimestamp?: string; - outputTimestamp?: string; -} & ( - | { status: "pending" } - | { status: "formatted"; formatted: FormattedCode } - | { status: "success"; formatted: FormattedCode; result: unknown } - | { status: "error"; formatted: FormattedCode | undefined; error: unknown } -); - -export type ContainerStatus = - | { type: "ready" } - | { type: "error"; error: unknown } - | { type: "pending" } - | { type: "unsupported"; error: unknown } - | { type: "unknown" }; - -export type ContainerState = { - status: ContainerStatus; - commands: ReplCommand[]; -} & InspectData; - -export class ActorWorkerContainer { - #state: ContainerState = { - status: { type: "unknown" }, - commands: [], - rpcs: [], - state: { enabled: false, value: undefined }, - connections: [], - }; - - #meta: { - actorId: string; - } | null = null; - - #opts: { - notifyOnReconnect?: boolean; - } | null = null; - - #listeners: (() => void)[] = []; - #worker: Worker | undefined; - - // - async init({ - actorId, - endpoint, - signal, - notifyOnReconnect, - }: { - actorId: string; - endpoint: string; - signal: AbortSignal; - notifyOnReconnect?: boolean; - }) { - this.terminate(); - - this.#meta = { actorId }; - this.#opts = { notifyOnReconnect }; - this.#state.status = { type: "pending" }; - this.#update(); - try { - signal.throwIfAborted(); - - // FIXME(RVT-4553) - // if (actor.resources.cpu !== 125 || actor.resources.memory !== 128) { - // throw new Error("Unsupported actor resources"); - // } - - // If we reached this point, the actor is supported - // check if we still operate on the same actor - if (this.#meta.actorId !== actorId) { - // if not, we don't need to do anything - return null; - } - - const worker = new ActorWorker({ name: `actor-${actorId}` }); - signal.throwIfAborted(); - // now worker needs to check if the actor is supported - this.#setupWorker(worker, { actorId, endpoint }); - signal.throwIfAborted(); - return worker; - } catch (e) { - console.log(e); - // If we reached this point, the actor is not supported - // check if we still operate on the same actor - if (e instanceof DOMException && e.name === "AbortError") { - return null; - } - - if (e instanceof CancelledError) { - this.#worker?.terminate(); - this.#worker = undefined; - return null; - } - - this.#worker?.terminate(); - this.#worker = undefined; - this.#state.status = { type: "unsupported", error: e }; - this.#update(); - } - return null; - } - - terminate() { - this.#worker?.terminate(); - this.#worker = undefined; - this.#state.commands = []; - this.#state.status = { type: "unknown" }; - this.#state.rpcs = []; - this.#state.state = { - enabled: false, - value: undefined, - }; - this.#meta = null; - this.#opts = null; - this.#state.connections = []; - this.#update(); - } - - #setupWorker(worker: Worker, data: Omit) { - this.#worker = worker; - this.#worker.addEventListener("message", (event) => { - try { - this.#handleMessage(event); - } catch (e) { - console.error(e); - this.#state.status = { type: "error", error: e }; - this.#update(); - } - }); - - this.#worker.addEventListener("error", (error) => { - console.log(error, error.message, error.error); - }); - - this.#worker.postMessage({ - type: "init", - ...data, - token: ls.get("rivet-token")?.token, - } satisfies InitMessage); - } - - run(data: string) { - const key = Date.now().toString(); - this.#state.commands = [ - ...this.#state.commands, - { status: "pending", code: data, key, logs: [] }, - ]; - - this.#worker?.postMessage({ - type: "code", - data, - id: key, - } satisfies CodeMessage); - this.#update(); - } - - setState(data: string) { - this.#worker?.postMessage({ - type: "set-state", - data, - } satisfies SetStateMessage); - this.#state.state = { - ...this.#state.state, - value: JSON.parse(data || "{}"), - }; - this.#update(); - } - - getCommands() { - return this.#state.commands; - } - - getStatus() { - return this.#state.status; - } - - getRpcs() { - return this.#state.rpcs; - } - - getState() { - return this.#state.state; - } - - getConnections() { - return this.#state.connections; - } - - subscribe(cb: () => void) { - this.#listeners.push(cb); - return () => { - this.#listeners = this.#listeners.filter( - (listener) => listener !== cb, - ); - }; - } - - #handleMessage(event: MessageEvent) { - const { success, data: msg } = ResponseSchema.safeParse(event.data); - - if (!success) { - return; - } - - if (msg.type === "formatted") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - inputTimestamp: new Date().toISOString(), - ...command, - status: "formatted", - formatted: msg.data, - } satisfies ReplCommand; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "result") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - outputTimestamp: new Date().toISOString(), - ...command, - status: "success", - result: msg.data, - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "log") { - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - ...command, - logs: [...command.logs, msg.data], - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "error") { - if (!msg.id) { - this.#state.status = { type: "error", error: msg.data }; - console.error("Actor Worker Error", msg.data); - this.#update(); - return; - } - - const command = this.#state.commands.find( - (command) => command.key === msg.id, - ); - if (command) { - const newCommand = { - outputTimestamp: new Date().toISOString(), - ...command, - status: "error", - error: msg.data, - }; - Object.assign(command, newCommand); - this.#state.commands = [...this.#state.commands]; - this.#update(); - } - } - - if (msg.type === "ready") { - if (this.#opts?.notifyOnReconnect) { - toast.success("Connected to Actor", { - id: "ac-ws-reconnect", - }); - } - this.#state.status = { type: "ready" }; - } - - if (msg.type === "inspect" || msg.type === "ready") { - this.#state.rpcs = [...msg.data.rpcs]; - this.#state.state = { - ...msg.data.state, - value: msg.data.state.value || {}, - }; - this.#state.connections = [...msg.data.connections]; - this.#update(); - } - - if (msg.type === "lost-connection") { - this.#state.status = { type: "pending" }; - - if (this.#opts?.notifyOnReconnect) { - toast.loading("Reconnecting...", { id: "ac-ws-reconnect" }); - } - this.#update(); - } - } - - #update() { - for (const listener of this.#listeners) { - listener(); - } - } -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-context.tsx b/frontend/packages/components/src/actors/worker/actor-worker-context.tsx deleted file mode 100644 index 0d731caae6..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-context.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useAtomValue } from "jotai"; -import { selectAtom } from "jotai/utils"; -import { - type ReactNode, - createContext, - useCallback, - useContext, - useEffect, - useState, - useSyncExternalStore, -} from "react"; -import { toast } from "sonner"; -import { assertNonNullable } from "../../lib/utils"; -import { type Actor, type ActorAtom, ActorFeature } from "../actor-context"; -import { ActorWorkerContainer } from "./actor-worker-container"; - -export const ActorWorkerContext = createContext( - null, -); - -export const useActorWorker = () => { - const value = useContext(ActorWorkerContext); - assertNonNullable(value); - return value; -}; - -const selector = (a: Actor) => ({ - actorId: a.id, - endpoint: a.endpoint, - enabled: - !a.destroyedAt && - a.endpoint !== null && - a.startedAt !== null && - a.features?.includes(ActorFeature.Console), -}); - -interface ActorWorkerContextProviderProps { - actor: ActorAtom; - children: ReactNode; - notifyOnReconnect?: boolean; -} - -// FIXME: rewrite with jotai -export const ActorWorkerContextProvider = ({ - children, - actor, - notifyOnReconnect, -}: ActorWorkerContextProviderProps) => { - const { actorId, endpoint, enabled } = useAtomValue( - selectAtom(actor, selector), - ); - - const [container] = useState( - () => new ActorWorkerContainer(), - ); - - // biome-ignore lint/correctness/useExhaustiveDependencies: we want to create worker on each of those props change - useEffect(() => { - const ctrl = new AbortController(); - - if (enabled && endpoint) { - container.init({ - actorId, - endpoint, - notifyOnReconnect, - signal: ctrl.signal, - }); - } else { - toast.dismiss("ac-ws-reconnect"); - } - - return () => { - ctrl.abort(); - container.terminate(); - }; - }, [actorId, endpoint, enabled]); - - return ( - - {children} - - ); -}; - -export function useActorReplCommands() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getCommands(); - }, [container]), - ); -} - -export function useActorWorkerStatus() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getStatus(); - }, [container]), - ); -} - -export function useActorRpcs() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getRpcs(); - }, [container]), - ); -} - -export function useActorState() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getState(); - }, [container]), - ); -} - -export function useActorConnections() { - const container = useActorWorker(); - return useSyncExternalStore( - useCallback( - (cb) => { - return container.subscribe(cb); - }, - [container], - ), - useCallback(() => { - return container.getConnections(); - }, [container]), - ); -} diff --git a/frontend/packages/components/src/actors/worker/actor-worker-schema.ts b/frontend/packages/components/src/actors/worker/actor-worker-schema.ts deleted file mode 100644 index 755bb57b59..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-schema.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { InspectDataSchema } from "actor-core/inspector/protocol/actor"; -import { z } from "zod"; - -export type ReplErrorCode = - | "unsupported" - | "runtime_error" - | "timeout" - | "syntax"; - -const CodeMessageSchema = z.object({ - type: z.literal("code"), - data: z.string(), - id: z.string(), -}); -const InitMessageSchema = z.object({ - type: z.literal("init"), - endpoint: z.string(), - actorId: z.string(), - token: z.string().optional(), -}); - -const SetStateMessageSchema = z.object({ - type: z.literal("set-state"), - data: z.string(), -}); - -export const MessageSchema = z.discriminatedUnion("type", [ - CodeMessageSchema, - InitMessageSchema, - SetStateMessageSchema, -]); - -export const FormattedCodeSchema = z - .object({ - fg: z.string().optional(), - tokens: z.array( - z.array( - z.object({ - content: z.string(), - color: z.string().optional(), - }), - ), - ), - }) - .catch((ctx) => ctx.input); - -export const LogSchema = z.object({ - method: z.union([z.literal("log"), z.literal("warn"), z.literal("error")]), - data: z.array(z.any()).optional(), - timestamp: z.string().optional(), -}); - -export const ResponseSchema = z.discriminatedUnion("type", [ - z.object({ - type: z.literal("error"), - id: z.string().optional(), - data: z.any(), - }), - z.object({ - type: z.literal("formatted"), - id: z.string(), - data: FormattedCodeSchema, - }), - z.object({ - type: z.literal("result"), - id: z.string(), - data: z.any().optional(), - }), - z.object({ - type: z.literal("log"), - id: z.string(), - data: LogSchema, - }), - z.object({ - type: z.literal("ready"), - data: InspectDataSchema, - }), - z.object({ - type: z.literal("inspect"), - data: InspectDataSchema, - }), - z.object({ - type: z.literal("lost-connection"), - }), -]); - -export type Response = z.infer; -export type Message = z.infer; -export type FormattedCode = z.infer; -export type Log = z.infer; -export type InitMessage = z.infer; -export type CodeMessage = z.infer; -export type InspectData = z.infer; -export type SetStateMessage = z.infer; diff --git a/frontend/packages/components/src/actors/worker/actor-worker-status.tsx b/frontend/packages/components/src/actors/worker/actor-worker-status.tsx deleted file mode 100644 index 7838351392..0000000000 --- a/frontend/packages/components/src/actors/worker/actor-worker-status.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Icon, faExclamationTriangle, faSpinner } from "@rivet-gg/icons"; -import { AnimatePresence, motion } from "framer-motion"; -import type { ContainerStatus } from "./actor-worker-container"; - -interface ActorWorkerStatusProps { - status: ContainerStatus["type"]; -} - -export function ActorWorkerStatus({ status }: ActorWorkerStatusProps) { - return ( - - {status === "pending" ? ( - - - Connecting to Actor... - - ) : null} - {status === "error" ? ( - - - Couldn't connect to Actor. - - ) : null} - {status === "unsupported" ? ( - - - Console is not supported for this Actor. - - ) : null} - - ); -} diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 3934bb85a8..231cdd81f7 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -24,10 +24,19 @@ declare module "@tanstack/react-router" { } } +declare module "@tanstack/history" { + interface HistoryState { + // inspector specific + // indicates that the inspector was already connected as the result of submission of connection form + connectedInForm?: boolean; + } +} + declare module "@tanstack/react-query" { interface Register { queryMeta: { mightRequireAuth?: boolean; + statusCheck?: boolean; }; } } diff --git a/frontend/src/app/actor-builds-list.tsx b/frontend/src/app/actor-builds-list.tsx index c4c3423580..a6a9c7425a 100644 --- a/frontend/src/app/actor-builds-list.tsx +++ b/frontend/src/app/actor-builds-list.tsx @@ -24,7 +24,7 @@ export function ActorBuildsList() { ) : null} {data?.map((build) => ( diff --git a/frontend/src/app/actors.tsx b/frontend/src/app/actors.tsx index aae11f8c78..e67302a8dd 100644 --- a/frontend/src/app/actors.tsx +++ b/frontend/src/app/actors.tsx @@ -1,7 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { - ActorFeature, type ActorId, ActorNotFound, ActorsActorDetails, @@ -13,17 +12,7 @@ import { export function Actors({ actorId }: { actorId: string | undefined }) { return ( - {actorId ? ( - - ) : ( - - )} + {actorId ? : } ); } @@ -37,16 +26,7 @@ function Actor() { ); if (!data || isError) { - return ( - - ); + return ; } return ( diff --git a/frontend/src/app/build-prefiller.tsx b/frontend/src/app/build-prefiller.tsx index a1aae2f444..abfb643424 100644 --- a/frontend/src/app/build-prefiller.tsx +++ b/frontend/src/app/build-prefiller.tsx @@ -11,7 +11,7 @@ export function BuildPrefiller() { return ( ({ ...search, n: [data[0].name] })} + search={(search) => ({ ...search, n: [data[0].id] })} /> ); } diff --git a/frontend/src/app/connect.tsx b/frontend/src/app/connect.tsx index eaa0a68509..49576d0b13 100644 --- a/frontend/src/app/connect.tsx +++ b/frontend/src/app/connect.tsx @@ -29,7 +29,7 @@ export function Connect({

Get started with one of our quick start guides:

-
+

- Connect to your RivetKit project by entering the URL and - access token. + Connect to your Rivet Project by entering your RivetKit + URL.

diff --git a/frontend/src/app/credentials-context.tsx b/frontend/src/app/credentials-context.tsx deleted file mode 100644 index d40ea1dd47..0000000000 --- a/frontend/src/app/credentials-context.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext, useContext } from "react"; - -export type InspectorCredentials = { - url: string; - token: string; -}; - -export const InspectorCredentialsContext = createContext<{ - credentials: InspectorCredentials | null; - setCredentials: (creds: InspectorCredentials | null) => void; -}>({ credentials: null, setCredentials: () => {} }); - -export const useInspectorCredentials = () => { - const ctx = useContext(InspectorCredentialsContext); - return ctx; -}; - -export const InspectorCredentialsProvider = - InspectorCredentialsContext.Provider; diff --git a/frontend/src/app/data-providers/default-data-provider.tsx b/frontend/src/app/data-providers/default-data-provider.tsx index 0b3c0a25f5..ce92117204 100644 --- a/frontend/src/app/data-providers/default-data-provider.tsx +++ b/frontend/src/app/data-providers/default-data-provider.tsx @@ -1,25 +1,14 @@ +import type { Rivet } from "@rivetkit/engine-api-full"; import { infiniteQueryOptions, + keepPreviousData, type MutationOptions, mutationOptions, type QueryKey, queryOptions, - UseInfiniteQueryOptions, } from "@tanstack/react-query"; -import type { - ActorId, - ActorLogEntry, - CreateActor as InspectorCreateActor, -} from "rivetkit/inspector"; import { z } from "zod"; -import { - type Actor, - type ActorMetrics, - type Build, - type CrashPolicy, - getActorStatus, - type Region, -} from "@/components/actors"; +import { type ActorId, getActorStatus } from "@/components/actors"; import { queryClient } from "@/queries/global"; export const ActorQueryOptionsSchema = z @@ -53,20 +42,7 @@ export type ActorQueryOptions = z.infer; export const RECORDS_PER_PAGE = 10; -type PaginatedResponse = { - pagination: { cursor?: string }; -} & Record; - -type PaginatedActorResponse = PaginatedResponse; -type PaginatedBuildsResponse = PaginatedResponse; -type PaginatedRegionsResponse = PaginatedResponse; - -type CreateActor = Omit & { - runnerNameSelector: string; - key: string; - crashPolicy: CrashPolicy; - datacenter?: string; -}; +type CreateActor = Omit; const defaultContext = { endpoint: "", @@ -82,23 +58,10 @@ const defaultContext = { refetchInterval: 2000, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedActorResponse; - }, - getNextPageParam: (lastPage) => { - if (lastPage.pagination.cursor) { - return lastPage.pagination.cursor; - } - - if ( - !lastPage || - lastPage.actors.length === 0 || - lastPage.actors.length < RECORDS_PER_PAGE - ) { - return undefined; - } - - return lastPage.actors[lastPage.actors.length - 1].id; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.ActorsListResponse; }, + getNextPageParam: (lastPage) => lastPage.pagination.cursor, }); }, @@ -110,13 +73,21 @@ const defaultContext = { refetchInterval: 2000, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedBuildsResponse; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.ActorsListNamesResponse; }, getNextPageParam: () => { return undefined; }, select: (data) => { - return data.pages.flatMap((page) => page.builds); + return data.pages.flatMap((page) => + Array.from( + Object.entries(page.names).map(([id, name]) => ({ + id, + name, + })), + ), + ); }, }); }, @@ -126,7 +97,7 @@ const defaultContext = { ...this.buildsQueryOptions(), select: (data) => { return data.pages.reduce((acc, page) => { - return acc + page.builds.length; + return acc + Object.keys(page.names).length; }, 0); }, }); @@ -139,7 +110,7 @@ const defaultContext = { refetchInterval: 5000, select: (data) => { return data.pages.flatMap((page) => - page.actors.map((actor) => actor.id), + page.actors.map((actor) => actor.actorId), ); }, }); @@ -150,7 +121,7 @@ const defaultContext = { ...this.actorsQueryOptions(opts), select: (data) => { return data.pages.flatMap((page) => - page.actors.map((actor) => actor.id), + page.actors.map((actor) => actor.actorId), ).length; }, }); @@ -160,24 +131,17 @@ const defaultContext = { actorQueryOptions(actorId: ActorId) { return queryOptions({ queryFn: async () => { - return {} as Actor; + return {} as Rivet.Actor; }, queryKey: ["actor", actorId] as QueryKey, }); }, - actorRegionQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.region, - }); - }, - actorDestroyedAtQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => - data.destroyedAt ? new Date(data.destroyedAt) : null, + data.destroyTs ? new Date(data.destroyTs) : null, }); }, @@ -191,58 +155,39 @@ const defaultContext = { actorStatusAdditionalInfoQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), - select: ({ rescheduleAt }) => ({ - rescheduleAt, + select: ({ rescheduleTs }) => ({ + rescheduleTs, }), }); }, - actorFeaturesQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.features ?? [], - }); - }, - actorGeneralQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => ({ - tags: data.tags, keys: data.key, - createdAt: data.createdAt ? new Date(data.createdAt) : null, - destroyedAt: data.destroyedAt - ? new Date(data.destroyedAt) - : null, - connectableAt: data.connectableAt - ? new Date(data.connectableAt) + createTs: data.createTs ? new Date(data.createTs) : null, + destroyTs: data.destroyTs ? new Date(data.destroyTs) : null, + connectableTs: data.connectableTs + ? new Date(data.connectableTs) : null, - pendingAllocationAt: data.pendingAllocationAt - ? new Date(data.pendingAllocationAt) + pendingAllocationTs: data.pendingAllocationTs + ? new Date(data.pendingAllocationTs) : null, - sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, - region: data.region, - runner: data.runner, + sleepTs: data.sleepTs ? new Date(data.sleepTs) : null, + datacenter: data.datacenter, + runner: data.runnerNameSelector, crashPolicy: data.crashPolicy, }), }); }, - actorBuildQueryOptions(actorId: ActorId) { - return queryOptions({ - queryKey: ["actor", actorId, "build"] as QueryKey, - queryFn: async () => { - throw new Error("Not implemented"); - return {} as Build; - }, - enabled: false, - }); - }, actorMetricsQueryOptions(actorId: ActorId) { return queryOptions({ queryKey: ["actor", actorId, "metrics"] as QueryKey, queryFn: async () => { throw new Error("Not implemented"); - return {} as ActorMetrics; + // biome-ignore lint/correctness/noUnreachable: stub + return {}; }, enabled: false, }); @@ -283,81 +228,47 @@ const defaultContext = { initialPageParam: null as string | null, queryFn: async () => { throw new Error("Not implemented"); - return [] as ActorLogEntry[]; + // biome-ignore lint/correctness/noUnreachable: stub + return []; }, getNextPageParam: () => null, }); }, - actorNetworkQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: (data) => data.network, - }); - }, - actorNetworkPortsQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorNetworkQueryOptions(actorId), - select: (data) => data.network?.ports, - }); - }, - actorRuntimeQueryOptions(actorId: ActorId) { - return queryOptions({ - ...this.actorQueryOptions(actorId), - select: ({ runtime, lifecycle, tags }) => ({ - runtime, - lifecycle, - tags, - }), - }); - }, actorWorkerQueryOptions(actorId: ActorId) { return queryOptions({ ...this.actorQueryOptions(actorId), select: (data) => ({ - features: data.features ?? [], name: data.name ?? null, endpoint: this.endpoint ?? null, - destroyedAt: data.destroyedAt - ? new Date(data.destroyedAt) - : null, - runner: data.runner ?? undefined, - sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, - startedAt: data.startedAt ? new Date(data.startedAt) : null, + destroyedAt: data.destroyTs ? new Date(data.destroyTs) : null, + runner: data.runnerNameSelector ?? undefined, + sleepingAt: data.sleepTs ? new Date(data.sleepTs) : null, + startedAt: data.startTs ? new Date(data.startTs) : null, }), }); }, // #endregion - regionsQueryOptions() { + datacentersQueryOptions() { return infiniteQueryOptions({ queryKey: ["actor", "regions"] as QueryKey, initialPageParam: null as string | null, queryFn: async () => { throw new Error("Not implemented"); - return {} as PaginatedRegionsResponse; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.DatacentersListResponse; }, getNextPageParam: () => null, - select: (data) => data.pages.flatMap((page) => page.regions), + select: (data) => data.pages.flatMap((page) => page.datacenters), }); }, - regionQueryOptions(regionId: string | undefined) { + datacenterQueryOptions(regionId: string | undefined) { return queryOptions({ queryKey: ["actor", "region", regionId] as QueryKey, enabled: !!regionId, queryFn: async () => { throw new Error("Not implemented"); - return {} as Region; - }, - }); - }, - statusQueryOptions() { - return queryOptions({ - queryKey: ["status"] as QueryKey, - refetchInterval: 1000, - enabled: false, - retry: 0, - queryFn: async () => { - throw new Error("Not implemented"); - return false as boolean; + // biome-ignore lint/correctness/noUnreachable: stub + return {} as Rivet.Datacenter; }, }); }, @@ -366,6 +277,7 @@ const defaultContext = { mutationKey: ["createActor"] as QueryKey, mutationFn: async (_: CreateActor) => { throw new Error("Not implemented"); + // biome-ignore lint/correctness/noUnreachable: stub return ""; }, onSuccess: () => { @@ -380,6 +292,35 @@ const defaultContext = { }, }); }, + + actorInspectorTokenQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: 1000, + gcTime: 1000, + queryKey: ["tokens", actorId, "inspector"] as QueryKey, + queryFn: async () => { + throw new Error("Not implemented"); + // biome-ignore lint/correctness/noUnreachable: stub + return "" as string | null; + }, + }); + }, + + statusQueryOptions() { + return queryOptions({ + queryKey: ["status"] as QueryKey, + queryFn: async () => { + throw new Error("Not implemented"); + // biome-ignore lint/correctness/noUnreachable: stub + return true as boolean; + }, + enabled: false, + refetchInterval: 5000, + meta: { + statusCheck: true, + }, + }); + }, }; export type DefaultDataProvider = typeof defaultContext; diff --git a/frontend/src/app/data-providers/engine-data-provider.tsx b/frontend/src/app/data-providers/engine-data-provider.tsx index 3a651e6fc3..72da20b007 100644 --- a/frontend/src/app/data-providers/engine-data-provider.tsx +++ b/frontend/src/app/data-providers/engine-data-provider.tsx @@ -5,16 +5,10 @@ import { mutationOptions, type QueryKey, queryOptions, - UseQueryOptions, } from "@tanstack/react-query"; import z from "zod"; import { getConfig, ls } from "@/components"; -import { - type Actor, - ActorFeature, - type ActorId, - type CrashPolicy, -} from "@/components/actors"; +import type { ActorId } from "@/components/actors"; import { engineEnv } from "@/lib/env"; import { convertStringToId } from "@/lib/utils"; import { noThrow, shouldRetryAllExpect403 } from "@/queries/utils"; @@ -141,39 +135,17 @@ export const createNamespaceContext = ({ canCreateActors: true, canDeleteActors: true, }, - statusQueryOptions() { - return queryOptions({ - ...def.statusQueryOptions(), - queryKey: [{ namespace }, ...def.statusQueryOptions().queryKey], - enabled: true, - queryFn: async () => { - return true; - }, - retry: shouldRetryAllExpect403, - throwOnError: noThrow, - meta: { - mightRequireAuth, - }, - }); - }, - regionsQueryOptions() { + datacentersQueryOptions() { return infiniteQueryOptions({ - ...def.regionsQueryOptions(), + ...def.datacentersQueryOptions(), enabled: true, queryKey: [ { namespace }, - ...def.regionsQueryOptions().queryKey, + ...def.datacentersQueryOptions().queryKey, ] as QueryKey, queryFn: async () => { const data = await client.datacenters.list(); - return { - regions: data.datacenters.map((dc) => ({ - id: dc.name, - name: dc.name, - url: dc.url, - })), - pagination: data.pagination, - }; + return data; }, retry: shouldRetryAllExpect403, throwOnError: noThrow, @@ -182,27 +154,27 @@ export const createNamespaceContext = ({ }, }); }, - regionQueryOptions(regionId: string | undefined) { + datacenterQueryOptions(name: string | undefined) { return queryOptions({ - ...def.regionQueryOptions(regionId), + ...def.datacenterQueryOptions(name), queryKey: [ { namespace }, - ...def.regionQueryOptions(regionId).queryKey, + ...def.datacenterQueryOptions(name).queryKey, ], queryFn: async ({ client }) => { const regions = await client.ensureInfiniteQueryData( - this.regionsQueryOptions(), + this.datacentersQueryOptions(), ); for (const page of regions.pages) { - for (const region of page.regions) { - if (region.id === regionId) { + for (const region of page.datacenters) { + if (region.name === name) { return region; } } } - throw new Error(`Region not found: ${regionId}`); + throw new Error(`Region not found: ${name}`); }, retry: shouldRetryAllExpect403, throwOnError: noThrow, @@ -229,7 +201,7 @@ export const createNamespaceContext = ({ throw new Error("Actor not found"); } - return transformActor(data.actors[0]); + return data.actors[0]; }, retry: shouldRetryAllExpect403, throwOnError: noThrow, @@ -293,12 +265,7 @@ export const createNamespaceContext = ({ { abortSignal }, ); - return { - ...data, - actors: data.actors.map((actor) => - transformActor(actor), - ), - }; + return data; }, getNextPageParam: (lastPage) => { if (lastPage.actors.length < RECORDS_PER_PAGE) { @@ -328,22 +295,9 @@ export const createNamespaceContext = ({ { abortSignal }, ); - return { - pagination: data.pagination, - builds: Object.keys(data.names) - .sort() - .map((build) => ({ - id: build, - name: build, - })), - }; - }, - getNextPageParam: (lastPage) => { - if (lastPage.builds.length < RECORDS_PER_PAGE) { - return undefined; - } - return lastPage.pagination.cursor; + return data; }, + getNextPageParam: (lastPage) => lastPage.pagination?.cursor, retry: shouldRetryAllExpect403, throwOnError: noThrow, meta: { @@ -414,6 +368,32 @@ export const createNamespaceContext = ({ }, }); }, + actorInspectorTokenQueryOptions(actorId: ActorId) { + return queryOptions({ + queryKey: [ + { namespace }, + "actors", + actorId, + "inspector-token", + ] as QueryKey, + enabled: !!actorId, + queryFn: async ({ signal: abortSignal }) => { + // todo: check metadata if we're running serverless or not + const response = await client.actorsKvGet( + actorId, + // NOTE: MUST match key used in engine for actor inspector tokens + Buffer.from(Uint8Array.from([3])).toString("base64"), + { abortSignal }, + ); + + if (!response.value) { + return null; + } + + return response.value; + }, + }); + }, }; return { @@ -674,47 +654,6 @@ export const createNamespaceContext = ({ }; }; -function transformActor(a: Rivet.Actor): Actor { - return { - id: a.actorId as ActorId, - name: a.name, - key: a.key ? a.key : undefined, - connectableAt: a.connectableTs - ? new Date(a.connectableTs).toISOString() - : undefined, - region: a.datacenter, - createdAt: new Date(a.createTs).toISOString(), - startedAt: a.startTs ? new Date(a.startTs).toISOString() : undefined, - destroyedAt: a.destroyTs - ? new Date(a.destroyTs).toISOString() - : undefined, - sleepingAt: a.sleepTs ? new Date(a.sleepTs).toISOString() : undefined, - pendingAllocationAt: a.pendingAllocationTs - ? new Date(a.pendingAllocationTs).toISOString() - : undefined, - crashPolicy: a.crashPolicy as CrashPolicy, - runner: a.runnerNameSelector, - rescheduleAt: a.rescheduleTs - ? new Date(a.rescheduleTs).toISOString() - : undefined, - features: [ - ActorFeature.Config, - ActorFeature.Connections, - ActorFeature.State, - ActorFeature.Console, - ActorFeature.Database, - ActorFeature.EventsMonitoring, - ], - }; -} - -type RunnerConfig = [ - string, - { - datacenters: Record; - }, -]; - export function hasMetadataProvider( metadata: unknown, ): metadata is { provider?: string } { diff --git a/frontend/src/app/data-providers/inspector-data-provider.tsx b/frontend/src/app/data-providers/inspector-data-provider.tsx index be6afd2d9b..3d3cf9df80 100644 --- a/frontend/src/app/data-providers/inspector-data-provider.tsx +++ b/frontend/src/app/data-providers/inspector-data-provider.tsx @@ -1,20 +1,18 @@ -import { infiniteQueryOptions } from "@tanstack/react-query"; -import { - createManagerInspectorClient, - type Actor as InspectorActor, -} from "rivetkit/inspector"; -import type { Actor, ActorId } from "@/components/actors"; -import { ensureTrailingSlash } from "@/lib/utils"; - +import { queryOptions } from "@tanstack/react-query"; import { createDefaultGlobalContext, type DefaultDataProvider, } from "./default-data-provider"; +import { + createClient, + createGlobalContext as createGlobalEngineContext, + createNamespaceContext as createNamespaceEngineContext, +} from "./engine-data-provider"; export const createGlobalContext = (opts: { url?: string; token?: string }) => { const def = createDefaultGlobalContext(); - if (!opts.url || !opts.token) { + if (!opts.url) { return { ...def, endpoint: opts.url, @@ -24,164 +22,39 @@ export const createGlobalContext = (opts: { url?: string; token?: string }) => { }, }; } - - const client = createClient({ url: opts.url, token: opts.token }); - return { + const client = createClient(opts.url, { token: opts.token || "" }); + const global = { ...def, endpoint: opts.url, features: { canCreateActors: true, canDeleteActors: false, }, + ...createGlobalEngineContext({ + engineToken: () => opts.token!, + }), + }; + + return { + ...global, + ...createNamespaceEngineContext({ + ...global, + namespace: "default", + engineToken: () => opts.token!, + client, + }), statusQueryOptions() { - return { - ...def.statusQueryOptions(), - enabled: true, - queryFn: async ({ signal }) => { - const status = await client.ping.$get({ signal }); - if (!status.ok) { - throw new Error("Failed to fetch manager status"); - } - return true; - }, - }; - }, - regionsQueryOptions() { - return infiniteQueryOptions({ - ...def.regionsQueryOptions(), - enabled: true, + return queryOptions({ + ...global.statusQueryOptions(), queryFn: async () => { - return { - regions: [{ id: "default", name: "Default" }], - pagination: {}, - }; - }, - }); - }, - actorQueryOptions(actorId) { - return { - ...def.actorQueryOptions(actorId), - enabled: true, - queryFn: async ({ signal }) => { - const response = await client.actor[":id"].$get({ - param: { id: actorId }, - // @ts-expect-error - signal, - }); + const response = await fetch(opts.url || ""); if (!response.ok) { - throw response; - } - return transformActor(await response.json()); - }, - }; - }, - actorsQueryOptions(opts) { - return infiniteQueryOptions({ - ...def.actorsQueryOptions(opts), - enabled: true, - initialPageParam: undefined, - queryFn: async ({ signal, pageParam }) => { - const actors = await client.actors.$get({ - query: { cursor: pageParam, limit: 10 }, - signal, - }); - if (!actors.ok) { - throw new Error("Failed to fetch actors"); + throw new Error("Failed to fetch status"); } - const response = await actors.json(); - - return { - actors: response.map((actor) => transformActor(actor)), - pagination: { - cursor: - response.length === 10 - ? response[9].id - : undefined, - }, - }; - }, - }); - }, - buildsQueryOptions() { - return infiniteQueryOptions({ - ...def.buildsQueryOptions(), - enabled: true, - initialPageParam: undefined, - queryFn: async ({ signal, pageParam }) => { - const builds = await client.builds.$get({ - query: { cursor: pageParam, limit: 10 }, - signal, - }); - if (!builds.ok) { - throw new Error("Failed to fetch builds"); - } - const response = await builds.json(); - - return { - builds: response.map((build) => ({ - id: build.name, - name: build.name, - })), - pagination: { - cursor: - response.length === 10 - ? response[9].name - : undefined, - }, - }; + return true; }, + enabled: Boolean(opts.url), }); }, - createActorMutationOptions() { - return { - ...def.createActorMutationOptions(), - mutationFn: async (data) => { - const response = await client.actors.$post({ - json: { - key: [data.key], - name: data.name, - input: data.input, - }, - }); - if (!response.ok) { - throw new Error("Failed to create actor"); - } - const json = await response.json(); - if (!json?.id) { - throw new Error("Failed to create actor"); - } - return json.id; - }, - }; - }, } satisfies DefaultDataProvider; }; - -function transformActor(a: InspectorActor): Actor { - return { - id: a.id as ActorId, - name: a.name, - key: a.key.join(" "), - createdAt: a.createdAt - ? new Date(a.createdAt).toISOString() - : undefined, - destroyedAt: a.destroyedAt - ? new Date(a.destroyedAt).toISOString() - : undefined, - startedAt: a.createdAt - ? new Date(a.createdAt).toISOString() - : undefined, - features: a.features, - }; -} - -export function createClient({ url, token }: { url: string; token: string }) { - const newUrl = new URL(url); - if (!newUrl.pathname.endsWith("inspect")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; - } - - return createManagerInspectorClient(newUrl.href, { - headers: { Authorization: `Bearer ${token}` }, - }); -} diff --git a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx index eb0065ab32..6990b53e95 100644 --- a/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverfull-frame.tsx @@ -64,11 +64,11 @@ export default function ConnectManualServerlfullFrameContent({ provider, }: ConnectManualServerlfullFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = @@ -373,7 +373,9 @@ const useSelectedDatacenter = () => { const datacenter = useWatch({ name: "datacenter" }); const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), + useEngineCompatDataProvider().datacenterQueryOptions( + datacenter || "auto", + ), ); return data?.url || engineEnv().VITE_APP_API_URL; diff --git a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx index 71e32cb2f1..611516bd81 100644 --- a/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx +++ b/frontend/src/app/dialogs/connect-manual-serverless-frame.tsx @@ -66,12 +66,12 @@ export default function ConnectManualServerlessFrameContent({ onClose, }: ConnectManualServerlessFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ; diff --git a/frontend/src/app/dialogs/connect-quick-railway-frame.tsx b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx index 5a3bf6631c..1c8561ebf0 100644 --- a/frontend/src/app/dialogs/connect-quick-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-railway-frame.tsx @@ -52,11 +52,11 @@ export default function ConnectQuickRailwayFrameContent({ onClose, }: ConnectQuickRailwayFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = diff --git a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx index 4acd3a570f..c614876b32 100644 --- a/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-quick-vercel-frame.tsx @@ -34,12 +34,12 @@ export default function ConnectQuickVercelFrameContent({ onClose, }: ConnectQuickVercelFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/app/dialogs/connect-railway-frame.tsx b/frontend/src/app/dialogs/connect-railway-frame.tsx index 99766ddb42..9adb28eb9a 100644 --- a/frontend/src/app/dialogs/connect-railway-frame.tsx +++ b/frontend/src/app/dialogs/connect-railway-frame.tsx @@ -66,11 +66,11 @@ export default function ConnectRailwayFrameContent({ onClose, }: ConnectRailwayFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); const prefferedRegionForRailway = @@ -359,7 +359,9 @@ export const useSelectedDatacenter = () => { const datacenter = useWatch({ name: "datacenter" }); const { data } = useQuery( - useEngineCompatDataProvider().regionQueryOptions(datacenter || "auto"), + useEngineCompatDataProvider().datacenterQueryOptions( + datacenter || "auto", + ), ); return data?.url || engineEnv().VITE_APP_API_URL; diff --git a/frontend/src/app/dialogs/connect-vercel-frame.tsx b/frontend/src/app/dialogs/connect-vercel-frame.tsx index e351a42785..b9d69f6e9d 100644 --- a/frontend/src/app/dialogs/connect-vercel-frame.tsx +++ b/frontend/src/app/dialogs/connect-vercel-frame.tsx @@ -89,12 +89,12 @@ export default function CreateProjectFrameContent({ onClose, }: CreateProjectFrameContentProps) { usePrefetchInfiniteQuery({ - ...useEngineCompatDataProvider().regionsQueryOptions(), + ...useEngineCompatDataProvider().datacentersQueryOptions(), pages: Infinity, }); const { data: datacenters } = useSuspenseInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/app/forms/connect-manual-serverless-form.tsx b/frontend/src/app/forms/connect-manual-serverless-form.tsx index 8ed1cdd5e1..217d9c8901 100644 --- a/frontend/src/app/forms/connect-manual-serverless-form.tsx +++ b/frontend/src/app/forms/connect-manual-serverless-form.tsx @@ -105,7 +105,7 @@ export const RunnerName = function RunnerName() { export const Datacenters = function Datacenter() { const { control } = useFormContext(); const { data, hasNextPage, fetchNextPage } = useInfiniteQuery( - useEngineCompatDataProvider().regionsQueryOptions(), + useEngineCompatDataProvider().datacentersQueryOptions(), ); return ( diff --git a/frontend/src/app/inspector-root.tsx b/frontend/src/app/inspector-root.tsx index 41877ad226..fbde7328aa 100644 --- a/frontend/src/app/inspector-root.tsx +++ b/frontend/src/app/inspector-root.tsx @@ -1,21 +1,20 @@ import { CatchBoundary, + useLocation, useNavigate, useRouteContext, useSearch, } from "@tanstack/react-router"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useRef } from "react"; import { match } from "ts-pattern"; import { Actors } from "./actors"; import { BuildPrefiller } from "./build-prefiller"; import { Connect } from "./connect"; -import { InspectorCredentialsProvider } from "./credentials-context"; -import { createClient } from "./data-providers/inspector-data-provider"; import { Logo } from "./logo"; import { RouteLayout } from "./route-layout"; export function InspectorRoot() { - const alreadyConnected = useRouteContext({ + const connectedInPreflight = useRouteContext({ from: "/_context/", select: (ctx) => match(ctx) @@ -26,40 +25,31 @@ export function InspectorRoot() { ) .otherwise(() => null), }); + const connectedInForm = useLocation({ + select: (loc) => + "connectedInForm" in loc.state + ? (loc.state.connectedInForm ?? false) + : false, + }); + + const alreadyConnected = connectedInPreflight || connectedInForm; + const navigate = useNavigate(); const search = useSearch({ from: "/_context" }); - const [credentials, setCredentials] = useState(alreadyConnected ? { url: search.u!, token: search.t! } : null); const formRef = useRef(null); - useEffect(() => { - if (search.t) { - formRef.current?.requestSubmit(); - } - }, []); - - const ctxValue = useMemo(() => { - return { credentials, setCredentials }; - }, [credentials]); - - if (credentials || alreadyConnected) { + if (alreadyConnected) { return ( - - - - - search.n?.join(",") ?? "no-build-name" - } - errorComponent={() => null} - > - {!search.n ? : null} - - - + + + search.n?.join(",") ?? "no-build-name"} + errorComponent={() => null} + > + {!search.n ? : null} + + ); } @@ -71,32 +61,29 @@ export function InspectorRoot() { formRef={formRef} onSubmit={async (values, form) => { try { - const client = createClient({ - url: values.username, - token: values.token, + const response = await fetch(values.url, { + method: "OPTIONS", }); - const resp = await client.ping.$get(); - if (!resp.ok) { - throw resp; + if (!response.ok) { + throw new Error("CORS preflight failed"); } await navigate({ to: "/", search: (old) => { return { ...old, - u: values.username, - t: values.token, + u: values.url, }; }, - }); - setCredentials({ - url: values.username, - token: values.token, + state: (old) => ({ + ...old, + connectedInForm: true, + }), }); } catch { - form.setError("token", { + form.setError("url", { message: - "Failed to connect. Please check your URL and token.", + "Failed to connect. Please check your URL, and CORS settings.", }); } }} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 3f409cdb95..5a0df5df7c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,9 +1,8 @@ -import { useClerk } from "@clerk/clerk-react"; import { faArrowUpRight, faBolt, faLink, - faServer, + faLinkSlash, faSpinnerThird, Icon, } from "@rivet-gg/icons"; @@ -40,19 +39,14 @@ import { ResizablePanelGroup, ScrollArea, Skeleton, + WithTooltip, } from "@/components"; -import { - useDataProvider, - useDataProviderCheck, - useInspectorDataProvider, -} from "@/components/actors"; +import { useDataProvider, useDataProviderCheck } from "@/components/actors"; import type { HeaderLinkProps } from "@/components/header/header-link"; import { ensureTrailingSlash } from "@/lib/utils"; import { ActorBuildsList } from "./actor-builds-list"; import { Changelog } from "./changelog"; import { ContextSwitcher } from "./context-switcher"; -import { useInspectorCredentials } from "./credentials-context"; -import { HelpDropdown } from "./help-dropdown"; import { NamespaceSelect } from "./namespace-select"; import { UserDropdown } from "./user-dropdown"; @@ -397,9 +391,6 @@ const Subnav = () => { return null; } - const hasDataProvider = useDataProviderCheck(); - const hasQuery = hasDataProvider && !!useDataProvider().buildsQueryOptions; - return (
{__APP_TYPE__ === "engine" ? ( @@ -412,14 +403,12 @@ const Subnav = () => { Connect ) : null} - {hasDataProvider && hasQuery ? ( -
- - Instances - - -
- ) : null} +
+ + Instances + + +
); }; @@ -467,11 +456,11 @@ function ConnectionStatus(): ReactNode { from: "/_context", select: (s) => s.u, }); - const data = useInspectorDataProvider(); - const { setCredentials } = useInspectorCredentials(); + const data = useDataProvider(); const { isLoading, isError, isSuccess } = useQuery( data.statusQueryOptions(), ); + const navigate = useNavigate(); if (isLoading) { return ( @@ -501,7 +490,7 @@ function ConnectionStatus(): ReactNode { variant="outline" size="xs" className="ml-2 text-foreground" - onClick={() => setCredentials(null)} + onClick={() => navigate({ to: ".", state: {} })} startIcon={} > Reconnect @@ -512,11 +501,26 @@ function ConnectionStatus(): ReactNode { if (isSuccess) { return ( -
+

Connected

{endpoint}

+ + navigate({ to: ".", state: {} })} + > + + + } + content="Disconnect" + />
); } diff --git a/frontend/src/components/actors/actor-build.tsx b/frontend/src/components/actors/actor-build.tsx deleted file mode 100644 index c19adb9ae1..0000000000 --- a/frontend/src/components/actors/actor-build.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import { Dd, DiscreteCopyButton, Dl, Dt, Flex } from "@/components"; -import { useDataProvider } from "./data-provider"; -import type { ActorId } from "./queries"; - -interface ActorBuildProps { - actorId: ActorId; -} - -export function ActorBuild({ actorId }: ActorBuildProps) { - const { data } = useQuery( - useDataProvider().actorBuildQueryOptions(actorId), - ); - - if (!data) { - return null; - } - - return ( -
-
-

Build

-
- -
-
ID
-
- - {data.id} - -
-
-
-
- ); -} diff --git a/frontend/src/components/actors/actor-clear-events-log-button.tsx b/frontend/src/components/actors/actor-clear-events-log-button.tsx index 3c35506e2a..37283911ef 100644 --- a/frontend/src/components/actors/actor-clear-events-log-button.tsx +++ b/frontend/src/components/actors/actor-clear-events-log-button.tsx @@ -1,26 +1,26 @@ import { faBroomWide, Icon } from "@rivet-gg/icons"; import { Button } from "../ui/button"; import { WithTooltip } from "../ui/tooltip"; -import { type ActorId, useActorClearEventsMutation } from "./queries"; +import type { ActorId } from "./queries"; export function ActorClearEventsLogButton({ actorId }: { actorId: ActorId }) { - const { mutate, isPending } = useActorClearEventsMutation(actorId); - - return ( - { - mutate(); - }} - > - - - } - /> - ); + // const { mutate, isPending } = useActorClearEventsMutation(actorId); + return null; + // return ( + // { + // mutate(); + // }} + // > + // + // + // } + // /> + // ); } diff --git a/frontend/src/components/actors/actor-config-tab.tsx b/frontend/src/components/actors/actor-config-tab.tsx index 19b9b5841c..9e9342e099 100644 --- a/frontend/src/components/actors/actor-config-tab.tsx +++ b/frontend/src/components/actors/actor-config-tab.tsx @@ -1,8 +1,6 @@ import { faBooks, Icon } from "@rivet-gg/icons"; import { Button, DocsSheet, ScrollArea } from "@/components"; import { ActorGeneral } from "./actor-general"; -import { ActorNetwork } from "./actor-network"; -import { ActorRuntime } from "./actor-runtime"; import type { ActorId } from "./queries"; interface ActorConfigTabProps { @@ -24,8 +22,6 @@ export function ActorConfigTab(props: ActorConfigTabProps) {
- - ); } diff --git a/frontend/src/components/actors/actor-connections-tab.tsx b/frontend/src/components/actors/actor-connections-tab.tsx index 36b04f402d..325da20259 100644 --- a/frontend/src/components/actors/actor-connections-tab.tsx +++ b/frontend/src/components/actors/actor-connections-tab.tsx @@ -1,9 +1,9 @@ import { useQuery } from "@tanstack/react-query"; import { LiveBadge, ScrollArea } from "@/components"; -import { useActor } from "./actor-queries-context"; import { ActorObjectInspector } from "./console/actor-inspector"; import { useDataProvider } from "./data-provider"; -import { type ActorId, useActorConnectionsStream } from "./queries"; +import { useActorInspector } from "./inspector-context"; +import type { ActorId } from "./queries"; interface ActorConnectionsTabProps { actorId: ActorId; @@ -14,14 +14,10 @@ export function ActorConnectionsTab({ actorId }: ActorConnectionsTabProps) { useDataProvider().actorDestroyedAtQueryOptions(actorId), ); - const actorQueries = useActor(); - const { - data: { connections } = {}, - isError, - isLoading, - } = useQuery(actorQueries.actorConnectionsQueryOptions(actorId)); - - // useActorConnectionsStream(actorId); + const inspector = useActorInspector(); + const { data = [] } = useQuery( + inspector.actorConnectionsQueryOptions(actorId), + ); if (destroyedAt) { return ( @@ -31,24 +27,6 @@ export function ActorConnectionsTab({ actorId }: ActorConnectionsTabProps) { ); } - if (isError) { - return ( -
- Connections Preview is currently unavailable. -
- See console/logs for more details. -
- ); - } - - if (isLoading) { - return ( -
- Loading connections... -
- ); - } - return (
@@ -57,7 +35,7 @@ export function ActorConnectionsTab({ actorId }: ActorConnectionsTabProps) {
diff --git a/frontend/src/components/actors/actor-context.tsx b/frontend/src/components/actors/actor-context.tsx deleted file mode 100644 index d62efbb389..0000000000 --- a/frontend/src/components/actors/actor-context.tsx +++ /dev/null @@ -1,4 +0,0 @@ -export { - createActorInspectorClient, - createManagerInspectorClient, -} from "rivetkit/inspector"; diff --git a/frontend/src/components/actors/actor-database.tsx b/frontend/src/components/actors/actor-database.tsx index fa65ed4f9a..918794f6e1 100644 --- a/frontend/src/components/actors/actor-database.tsx +++ b/frontend/src/components/actors/actor-database.tsx @@ -13,8 +13,8 @@ import { SelectValue, } from "../ui/select"; import { WithTooltip } from "../ui/tooltip"; -import { useActor } from "./actor-queries-context"; import { DatabaseTable } from "./database/database-table"; +import { useActorInspector } from "./inspector-context"; import type { ActorId } from "./queries"; interface ActorDatabaseProps { @@ -22,9 +22,9 @@ interface ActorDatabaseProps { } export function ActorDatabase({ actorId }: ActorDatabaseProps) { - const actorQueries = useActor(); + const actorInspector = useActorInspector(); const { data, refetch } = useQuery( - actorQueries.actorDatabaseQueryOptions(actorId), + actorInspector.actorDatabaseQueryOptions(actorId), ); const [table, setTable] = useState( () => data?.db?.[0]?.table.name, @@ -36,11 +36,13 @@ export function ActorDatabase({ actorId }: ActorDatabaseProps) { data: rows, refetch: refetchData, isLoading, - } = useQuery( - actorQueries.actorDatabaseRowsQueryOptions(actorId, selectedTable!, { - enabled: !!selectedTable, - }), - ); + } = useQuery({ + ...actorInspector.actorDatabaseRowsQueryOptions( + actorId, + selectedTable!, + ), + enabled: !!selectedTable, + }); const currentTable = data?.db?.find( (db) => db.table.name === selectedTable, @@ -126,7 +128,7 @@ function TableSelect({ onSelect: (table: string) => void; value: string | undefined; }) { - const actorQueries = useActor(); + const actorQueries = useActorInspector(); const { data: tables } = useQuery( actorQueries.actorDatabaseTablesQueryOptions(actorId), ); diff --git a/frontend/src/components/actors/actor-db-tab.tsx b/frontend/src/components/actors/actor-db-tab.tsx index f3fcc1097c..deccca9510 100644 --- a/frontend/src/components/actors/actor-db-tab.tsx +++ b/frontend/src/components/actors/actor-db-tab.tsx @@ -1,8 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { ActorDatabase } from "./actor-database"; -import { useActor } from "./actor-queries-context"; import { Info } from "./actor-state-tab"; import { useDataProvider } from "./data-provider"; +import { useActorInspector } from "./inspector-context"; import type { ActorId } from "./queries"; interface ActorDatabaseTabProps { @@ -14,12 +14,12 @@ export function ActorDatabaseTab({ actorId }: ActorDatabaseTabProps) { useDataProvider().actorDestroyedAtQueryOptions(actorId), ); - const actorQueries = useActor(); + const actorInspector = useActorInspector(); const { data: isEnabled, isLoading, isError, - } = useQuery(actorQueries.actorDatabaseEnabledQueryOptions(actorId)); + } = useQuery(actorInspector.actorDatabaseEnabledQueryOptions(actorId)); if (destroyedAt) { return Database Studio is unavailable for inactive Actors.; diff --git a/frontend/src/components/actors/actor-editable-state.tsx b/frontend/src/components/actors/actor-editable-state.tsx index f631915450..5bcc42cdbf 100644 --- a/frontend/src/components/actors/actor-editable-state.tsx +++ b/frontend/src/components/actors/actor-editable-state.tsx @@ -1,4 +1,5 @@ import { faRotateLeft, faSave, Icon } from "@rivet-gg/icons"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { AnimatePresence, motion } from "framer-motion"; import { useMemo, useRef, useState } from "react"; import { @@ -14,7 +15,8 @@ import { JsonCode, } from "@/components/code-mirror"; import { ActorStateChangeIndicator } from "./actor-state-change-indicator"; -import { type ActorId, useActorStatePatchMutation } from "./queries"; +import { useActorInspector } from "./inspector-context"; +import type { ActorId } from "./queries"; const isValidJson = (json: string | null): json is string => { if (!json) return false; @@ -28,34 +30,31 @@ const isValidJson = (json: string | null): json is string => { interface ActorEditableStateProps { actorId: ActorId; - state: unknown; } -export function ActorEditableState({ - state, - actorId, -}: ActorEditableStateProps) { +export function ActorEditableState({ actorId }: ActorEditableStateProps) { + const actorInspector = useActorInspector(); + const { data: state } = useQuery( + actorInspector.actorStateQueryOptions(actorId), + ); + const [isEditing, setIsEditing] = useState(false); const [value, setValue] = useState(null); - const ref = useRef(null); - const formatted = useMemo(() => { return JSON.stringify(state, null, 2); }, [state]); - const isValid = isValidJson(value) ? JSON.parse(value) : false; - const { mutateAsync, isPending } = useActorStatePatchMutation(actorId); - - // useActorStateStream(actorId); + const { mutateAsync, isPending } = useMutation( + actorInspector.actorStatePatchMutation(actorId), + ); return ( <>
{isEditing ? : } -
diff --git a/frontend/src/components/actors/actor-events-list.tsx b/frontend/src/components/actors/actor-events-list.tsx index 519a869e88..df64d66f93 100644 --- a/frontend/src/components/actors/actor-events-list.tsx +++ b/frontend/src/components/actors/actor-events-list.tsx @@ -9,10 +9,9 @@ import { import { useQuery } from "@tanstack/react-query"; import { format } from "date-fns"; import { type PropsWithChildren, useEffect, useRef } from "react"; -import type { RecordedRealtimeEvent } from "rivetkit/inspector"; import { Badge } from "../ui/badge"; -import { useActor } from "./actor-queries-context"; import { ActorObjectInspector } from "./console/actor-inspector"; +import { useActorInspector } from "./inspector-context"; import type { ActorId } from "./queries"; interface ActorEventsListProps { @@ -26,9 +25,9 @@ export function ActorEventsList({ search, filter, }: ActorEventsListProps) { - const actorQueries = useActor(); + const actorInspector = useActorInspector(); const { data, isLoading, isError } = useQuery( - actorQueries.actorEventsQueryOptions(actorId), + actorInspector.actorEventsQueryOptions(actorId), ); if (isLoading) { @@ -45,7 +44,7 @@ export function ActorEventsList({ ); } - const filteredEvents = data?.events.filter?.((event) => { + const filteredEvents = data?.filter?.((event) => { const constraints = []; if ("name" in event) { diff --git a/frontend/src/components/actors/actor-events-tab.tsx b/frontend/src/components/actors/actor-events-tab.tsx index 6deb04786f..93a0cf4d13 100644 --- a/frontend/src/components/actors/actor-events-tab.tsx +++ b/frontend/src/components/actors/actor-events-tab.tsx @@ -1,8 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { ActorEvents } from "./actor-events"; -import { useActor } from "./actor-queries-context"; import { Info } from "./actor-state-tab"; import { useDataProvider } from "./data-provider"; +import { useActorInspector } from "./inspector-context"; import type { ActorId } from "./queries"; export type EventsTypeFilter = "action" | "subscription" | "broadcast" | "send"; @@ -17,7 +17,7 @@ export function ActorEventsTab({ actorId }: ActorEventsTabProps) { ); const { isError, isLoading } = useQuery( - useActor().actorEventsQueryOptions(actorId), + useActorInspector().actorEventsQueryOptions(actorId), ); if (destroyedAt) { diff --git a/frontend/src/components/actors/actor-events.tsx b/frontend/src/components/actors/actor-events.tsx index 225e809061..443facb07f 100644 --- a/frontend/src/components/actors/actor-events.tsx +++ b/frontend/src/components/actors/actor-events.tsx @@ -22,8 +22,8 @@ import { ActorClearEventsLogButton } from "./actor-clear-events-log-button"; import { useActorDetailsSettings } from "./actor-details-settings"; import { ActorDetailsSettingsButton } from "./actor-details-settings-button"; import { ActorEventsList } from "./actor-events-list"; -import { useActor } from "./actor-queries-context"; -import { type ActorId, useActorEventsStream } from "./queries"; +import { useActorInspector } from "./inspector-context"; +import type { ActorId } from "./queries"; export type EventsTypeFilter = "action" | "subscription" | "broadcast" | "send"; @@ -42,10 +42,9 @@ export function ActorEvents({ actorId }: ActorEventsProps) { const [isLive, setIsLive] = useState(true); const ref = useRef(null); - // useActorEventsStream(actorId, { enabled: isLive }); const [settings] = useActorDetailsSettings(); - const actorQueries = useActor(); + const actorQueries = useActorInspector(); const { data } = useQuery(actorQueries.actorEventsQueryOptions(actorId)); const { onScroll } = useScrollToBottom(ref, [data]); @@ -209,6 +208,7 @@ function useScrollToBottom( return () => { cancelAnimationFrame(rafId); }; + // biome-ignore lint/correctness/useExhaustiveDependencies: we track them separately }, deps); return { onScroll }; diff --git a/frontend/src/components/actors/actor-filters-context.tsx b/frontend/src/components/actors/actor-filters-context.tsx index 92ed89dbef..4b181f6180 100644 --- a/frontend/src/components/actors/actor-filters-context.tsx +++ b/frontend/src/components/actors/actor-filters-context.tsx @@ -43,12 +43,16 @@ export const ACTORS_FILTERS_DEFINITIONS = { category: "display", ephemeral: true, }, - showDatacenter: { - type: "boolean", - label: "Show Actors Datacenter", - category: "display", - ephemeral: true, - }, + ...(__APP_TYPE__ === "engine" || __APP_TYPE__ === "cloud" + ? { + showDatacenter: { + type: "boolean", + label: "Show Actors Datacenter", + category: "display", + ephemeral: true, + }, + } + : {}), wakeOnSelect: { type: "boolean", label: "Auto-wake Actors on select", diff --git a/frontend/src/components/actors/actor-network.tsx b/frontend/src/components/actors/actor-network.tsx deleted file mode 100644 index 3e6fb60c79..0000000000 --- a/frontend/src/components/actors/actor-network.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { faBooks, Icon } from "@rivet-gg/icons"; -import { useQuery } from "@tanstack/react-query"; -import { Fragment } from "react"; -import { - Button, - cn, - Dd, - DiscreteCopyButton, - Dl, - DocsSheet, - Dt, - Flex, -} from "@/components"; -import { ActorObjectInspector } from "./console/actor-inspector"; -import { useDataProvider } from "./data-provider"; -import type { ActorId } from "./queries"; - -export interface ActorNetworkProps { - actorId: ActorId; -} - -export function ActorNetwork({ actorId }: ActorNetworkProps) { - const { data: ports } = useQuery( - useDataProvider().actorNetworkPortsQueryOptions(actorId), - ); - if (!ports) { - return null; - } - - return ( -
-
-

Network

- - - -
-
- -
-
Ports
-
- {Object.entries(ports).map( - ([name, port], index) => ( - - - {name} - -
-
Protocol
-
- - {port.protocol} - -
-
Port
-
- - {port.port} - -
-
Hostname
-
- - - {port.hostname} - - -
- {port.url ? ( - <> -
URL
-
- - - {port.url} - - -
- - ) : null} - - {port.routing?.host ? ( - <> -
Host Routing
-
- - - -
- - ) : null} -
-
- ), - )} -
-
-
-
-
- ); -} diff --git a/frontend/src/components/actors/actor-not-found.tsx b/frontend/src/components/actors/actor-not-found.tsx index c5a28f1370..bba3f1b5f8 100644 --- a/frontend/src/components/actors/actor-not-found.tsx +++ b/frontend/src/components/actors/actor-not-found.tsx @@ -7,15 +7,9 @@ import { FilterOp } from "../ui/filters"; import { ActorTabs } from "./actors-actor-details"; import { useActorsView } from "./actors-view-context-provider"; import { useDataProvider } from "./data-provider"; -import type { ActorFeature, ActorId } from "./queries"; +import type { ActorId } from "./queries"; -export function ActorNotFound({ - actorId, - features = [], -}: { - features?: ActorFeature[]; - actorId?: ActorId; -}) { +export function ActorNotFound({ actorId }: { actorId?: ActorId }) { const { copy } = useActorsView(); const navigate = useNavigate(); @@ -30,7 +24,7 @@ export function ActorNotFound({ return (
- +
{!isLoading ? ( <> diff --git a/frontend/src/components/actors/actor-queries-context.tsx b/frontend/src/components/actors/actor-queries-context.tsx deleted file mode 100644 index c9ef62ab70..0000000000 --- a/frontend/src/components/actors/actor-queries-context.tsx +++ /dev/null @@ -1,270 +0,0 @@ -import { queryOptions } from "@tanstack/react-query"; -import { createContext, useContext } from "react"; -import { - createActorInspectorClient, - type RecordedRealtimeEvent, -} from "rivetkit/inspector"; -import type { ActorId } from "./queries"; - -type RequestOptions = Parameters[1]; - -export const createDefaultActorContext = ( - { hash }: { hash: string } = { hash: `${Date.now()}` }, -) => ({ - createActorInspectorFetchConfiguration: async ( - actorId: ActorId | string, - opts: { auth?: boolean } = { auth: true }, - ): Promise => ({ - headers: { - "X-RivetKit-Query": JSON.stringify({ - getForId: { actorId }, - }), - }, - }), - createActorInspectorUrl(actorId: ActorId | string) { - return "http://localhost:6420/registry/actors/inspect"; - }, - async createActorInspector( - actorId: ActorId | string, - opts: { auth?: boolean } = { auth: true }, - ) { - return createActorInspectorClient( - this.createActorInspectorUrl(actorId), - await this.createActorInspectorFetchConfiguration(actorId, opts), - ); - }, - actorPingQueryOptions( - actorId: ActorId, - opts: { enabled?: boolean; refetchInterval?: number | false } = {}, - ) { - return queryOptions({ - enabled: false, - refetchInterval: 1000, - ...opts, - queryKey: [hash, "actor", actorId, "ping"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.ping.$get(); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorStateQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "state"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.state.$get(); - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - enabled: boolean; - state: unknown; - }; - }, - }); - }, - - actorConnectionsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "connections"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.connections.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorDatabaseQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - queryKey: [hash, "actor", actorId, "database"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.db.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorDatabaseEnabledQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - ...this.actorDatabaseQueryOptions(actorId, { enabled }), - select: (data) => data.enabled, - notifyOnChangeProps: ["data", "isError", "isLoading"], - }); - }, - - actorDatabaseTablesQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - ...this.actorDatabaseQueryOptions(actorId, { enabled }), - select: (data) => - data.db?.map((table) => ({ - name: table.table.name, - type: table.table.type, - records: table.records, - })) || [], - notifyOnChangeProps: ["data", "isError", "isLoading"], - }); - }, - - actorDatabaseRowsQueryOptions( - actorId: ActorId, - table: string, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - staleTime: 0, - gcTime: 5000, - queryKey: [hash, "actor", actorId, "database", table], - queryFn: async ({ queryKey: [, , actorId, , table] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.db.$post({ - json: { query: `SELECT * FROM ${table} LIMIT 500` }, - }); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorEventsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - queryKey: [hash, "actor", actorId, "events"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.events.$get(); - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - events: RecordedRealtimeEvent[]; - }; - }, - }); - }, - - actorRpcsQueryOptions( - actorId: ActorId, - { enabled }: { enabled: boolean } = { enabled: true }, - ) { - return queryOptions({ - enabled, - queryKey: [hash, "actor", actorId, "rpcs"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId); - const response = await client.rpcs.$get(); - - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }); - }, - - actorClearEventsMutationOptions(actorId: ActorId) { - return { - mutationKey: [hash, "actor", actorId, "clear-events"], - mutationFn: async () => { - const client = await this.createActorInspector(actorId); - const response = await client.events.clear.$post(); - if (!response.ok) { - throw response; - } - return await response.json(); - }, - }; - }, - - actorWakeUpMutationOptions(actorId: ActorId) { - return { - mutationKey: [hash, "actor", actorId, "wake-up"], - mutationFn: async () => { - const client = await this.createActorInspector(actorId); - try { - await client.ping.$get(); - return true; - } catch { - return false; - } - }, - }; - }, - - actorAutoWakeUpQueryOptions( - actorId: ActorId, - { enabled }: { enabled?: boolean } = {}, - ) { - return queryOptions({ - enabled, - refetchInterval: 1000, - staleTime: 0, - gcTime: 0, - queryKey: [hash, "actor", actorId, "auto-wake-up"], - queryFn: async ({ queryKey: [, , actorId] }) => { - const client = await this.createActorInspector(actorId, { - auth: false, - }); - try { - await client.ping.$get(); - return true; - } catch { - return false; - } - }, - retry: false, - }); - }, -}); - -export type ActorContext = ReturnType; - -const ActorContext = createContext({} as ActorContext); - -export const useActor = () => useContext(ActorContext); - -export const ActorProvider = ActorContext.Provider; diff --git a/frontend/src/components/actors/actor-region.tsx b/frontend/src/components/actors/actor-region.tsx index 9a5f0b9a1c..348a5edaef 100644 --- a/frontend/src/components/actors/actor-region.tsx +++ b/frontend/src/components/actors/actor-region.tsx @@ -20,14 +20,14 @@ export function ActorRegion({ className, }: ActorRegionProps) { const { data: region } = useQuery( - useDataProvider().regionQueryOptions(regionId), + useDataProvider().datacenterQueryOptions(regionId), ); if (!regionId || !region) { return null; } - const regionKey = getRegionKey(region?.id); + const regionKey = getRegionKey(region?.name); if (showLabel) { return ( diff --git a/frontend/src/components/actors/actor-runtime.tsx b/frontend/src/components/actors/actor-runtime.tsx deleted file mode 100644 index c3803df811..0000000000 --- a/frontend/src/components/actors/actor-runtime.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Suspense } from "react"; -import { Skeleton } from "../ui/skeleton"; -import { ActorBuild } from "./actor-build"; -import type { ActorId } from "./queries"; - -export interface ActorRuntimeProps { - actorId: ActorId; -} - -export function ActorRuntime({ actorId }: ActorRuntimeProps) { - return ( - }> - - - ); -} diff --git a/frontend/src/components/actors/actor-state-tab.tsx b/frontend/src/components/actors/actor-state-tab.tsx index 9a130da3f6..e5b65b7d7c 100644 --- a/frontend/src/components/actors/actor-state-tab.tsx +++ b/frontend/src/components/actors/actor-state-tab.tsx @@ -5,6 +5,7 @@ import { Button } from "../ui/button"; import { ActorEditableState } from "./actor-editable-state"; import { useActor } from "./actor-queries-context"; import { useActorsView } from "./actors-view-context-provider"; +import { useActorInspector } from "./inspector-context"; import type { ActorId } from "./queries"; interface ActorStateTabProps { @@ -14,12 +15,13 @@ interface ActorStateTabProps { export function ActorStateTab({ actorId }: ActorStateTabProps) { const { links } = useActorsView(); - const actorQueries = useActor(); + const actorQueries = useActorInspector(); + const { - data: state, + data: isStateEnabled, isError, isLoading, - } = useQuery(actorQueries.actorStateQueryOptions(actorId)); + } = useQuery(actorQueries.actorIsStateEnabledQueryOptions(actorId)); if (isError) { return ( @@ -35,7 +37,7 @@ export function ActorStateTab({ actorId }: ActorStateTabProps) { return Loading state...; } - if (!state?.enabled) { + if (!isStateEnabled) { return (

@@ -51,7 +53,7 @@ export function ActorStateTab({ actorId }: ActorStateTabProps) { return (

- +
); } diff --git a/frontend/src/components/actors/actor-status-label.tsx b/frontend/src/components/actors/actor-status-label.tsx index 2faac42c55..34ed06518a 100644 --- a/frontend/src/components/actors/actor-status-label.tsx +++ b/frontend/src/components/actors/actor-status-label.tsx @@ -48,17 +48,17 @@ export function QueriedActorStatusAdditionalInfo({ }: { actorId: ActorId; }) { - const { data: { rescheduleAt } = {} } = useQuery( + const { data: { rescheduleTs } = {} } = useQuery( useDataProvider().actorStatusAdditionalInfoQueryOptions(actorId), ); - if (rescheduleAt) { + if (rescheduleTs) { return ( Will try to start again{" "} - ( - {formatISO(rescheduleAt)}){" "} + ( + {formatISO(rescheduleTs)}){" "} ); diff --git a/frontend/src/components/actors/actor-stop-button.tsx b/frontend/src/components/actors/actor-stop-button.tsx index 978f906aec..46b786ee07 100644 --- a/frontend/src/components/actors/actor-stop-button.tsx +++ b/frontend/src/components/actors/actor-stop-button.tsx @@ -41,6 +41,7 @@ export function ActorStopButton({ actorId }: ActorStopButtonProps) { return ( { - const { data: features = [] } = useQuery( - useDataProvider().actorFeaturesQueryOptions(actorId), - ); - - const supportsConsole = features.includes(ActorFeature.Console); - return (
- {supportsConsole ? : null} +
@@ -80,15 +71,11 @@ function Console({ actorId }: { actorId: ActorId }) { ); } -export const ActorsActorEmptyDetails = ({ - features, -}: { - features: ActorFeature[]; -}) => { +export const ActorsActorEmptyDetails = () => { const { copy } = useActorsView(); return (
- +

{copy.selectActor}

@@ -100,7 +87,6 @@ export const ActorsActorEmptyDetails = ({ export function ActorTabs({ tab, - features, onTabChange, actorId, className, @@ -109,22 +95,12 @@ export function ActorTabs({ }: { disabled?: boolean; tab?: string; - features: ActorFeature[]; onTabChange?: (tab: string) => void; actorId?: ActorId; className?: string; children?: ReactNode; }) { - const supportsState = features?.includes(ActorFeature.State); - const supportsLogs = features?.includes(ActorFeature.Logs); - const supportsConnections = features?.includes(ActorFeature.Connections); - const supportsMetadata = features?.includes(ActorFeature.Config); - const supportsMetrics = features?.includes(ActorFeature.Metrics); - const supportsEvents = features?.includes(ActorFeature.EventsMonitoring); - const supportsDatabase = features?.includes(ActorFeature.Database); - - const defaultTab = supportsState ? "state" : "logs"; - const value = disabled ? undefined : tab || defaultTab; + const value = disabled ? undefined : tab || "state"; const guardContent = useInspectorGuard(); @@ -138,69 +114,59 @@ export function ActorTabs({
- {supportsState ? ( - - State - - ) : null} - {supportsConnections ? ( - - Connections - - ) : null} - {supportsEvents ? ( - - Events - - ) : null} - {supportsDatabase ? ( - - Database - - ) : null} - {supportsLogs ? ( - + State + + + + Connections + + + + Events + + + {/* + Database + */} + + {/* Logs - - ) : null} - {supportsMetadata ? ( - - Metadata - - ) : null} - {supportsMetrics ? ( - */} + + Metadata + + {/* Metrics - - ) : null} + */} {actorId ? ( {actorId ? ( <> - {supportsLogs ? ( - - }> - {guardContent || ( - - )} - - - ) : null} - {supportsMetadata ? ( - - - - ) : null} - {supportsConnections ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsEvents ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsDatabase ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsState ? ( - - {guardContent || ( - - )} - - ) : null} - {supportsMetrics ? ( + + }> + {guardContent || } + + + + + + + {guardContent || ( + + )} + + + + {guardContent || } + + + {guardContent || } + + + {guardContent || } + + {/* {supportsMetrics ? ( )} - ) : null} + ) : null} */} ) : null} {children} diff --git a/frontend/src/components/actors/actors-list-row.tsx b/frontend/src/components/actors/actors-list-row.tsx index 22d70d1c1e..8fc5d29800 100644 --- a/frontend/src/components/actors/actors-list-row.tsx +++ b/frontend/src/components/actors/actors-list-row.tsx @@ -50,6 +50,7 @@ export const ActorsListRow = memo( {isVisible ? ( <> (null); diff --git a/frontend/src/components/actors/console/actor-console.tsx b/frontend/src/components/actors/console/actor-console.tsx index 02e0c328e2..e04b385c6e 100644 --- a/frontend/src/components/actors/console/actor-console.tsx +++ b/frontend/src/components/actors/console/actor-console.tsx @@ -3,8 +3,8 @@ import { useQuery } from "@tanstack/react-query"; import { AnimatePresence, motion } from "framer-motion"; import { useState } from "react"; import { Button, cn } from "@/components"; -import { useActor } from "../actor-queries-context"; import { useDataProvider } from "../data-provider"; +import { useActorInspector } from "../inspector-context"; import type { ActorId } from "../queries"; import { useActorWorkerStatus } from "../worker/actor-worker-context"; import { ActorWorkerStatus } from "../worker/actor-worker-status"; @@ -20,16 +20,15 @@ export function ActorConsole({ actorId }: ActorConsoleProps) { const status = useActorWorkerStatus(); const managerQueries = useDataProvider(); - const actorQueries = useActor(); + const actorInspector = useActorInspector(); const { data: { destroyedAt, sleepingAt } = {} } = useQuery( managerQueries.actorWorkerQueryOptions(actorId), ); - const { isSuccess, isError, isLoading } = useQuery( - actorQueries.actorPingQueryOptions(actorId, { - enabled: true, - refetchInterval: false, - }), - ); + const { isSuccess, isError, isLoading } = useQuery({ + ...actorInspector.actorPingQueryOptions(actorId), + enabled: true, + refetchInterval: false, + }); const isBlocked = status.type !== "ready" || !isSuccess || !!destroyedAt || !!sleepingAt; diff --git a/frontend/src/components/actors/crash-policy-select.tsx b/frontend/src/components/actors/crash-policy-select.tsx index b6dcc1b0e4..c5df9d6693 100644 --- a/frontend/src/components/actors/crash-policy-select.tsx +++ b/frontend/src/components/actors/crash-policy-select.tsx @@ -1,10 +1,12 @@ +import { Rivet } from "@rivetkit/engine-api-full"; import { Combobox } from "@/components"; -import { CrashPolicy } from "./queries"; -const VALUES = Array.from(Object.entries(CrashPolicy)).map(([key, value]) => ({ - label: key, - value, -})); +const VALUES = Array.from(Object.entries(Rivet.CrashPolicy)).map( + ([key, value]) => ({ + label: key, + value, + }), +); interface CrashPolicySelectProps { onValueChange: (value: string) => void; diff --git a/frontend/src/components/actors/dialogs/create-actor-dialog.tsx b/frontend/src/components/actors/dialogs/create-actor-dialog.tsx index c35e5d40c3..c9939e5e13 100644 --- a/frontend/src/components/actors/dialogs/create-actor-dialog.tsx +++ b/frontend/src/components/actors/dialogs/create-actor-dialog.tsx @@ -1,3 +1,4 @@ +import { Rivet } from "@rivetkit/engine-api-full"; import { useMutation } from "@tanstack/react-query"; import { useSearch } from "@tanstack/react-router"; import type { DialogContentProps } from "@/components/hooks"; @@ -18,7 +19,6 @@ import { Flex } from "../../ui/flex"; import { useActorsView } from "../actors-view-context-provider"; import { useDataProvider } from "../data-provider"; import * as ActorCreateForm from "../form/actor-create-form"; -import { CrashPolicy } from "../queries"; interface ContentProps extends DialogContentProps {} @@ -40,8 +40,10 @@ export default function CreateActorDialog({ onClose }: ContentProps) { name: values.name, input: values.input ? JSON.parse(values.input) : undefined, key: values.key, - datacenter: values.datacenter, - crashPolicy: values.crashPolicy || CrashPolicy.Restart, + datacenter: + __APP_TYPE__ === "inspector" ? "" : values.datacenter, + crashPolicy: + values.crashPolicy || Rivet.CrashPolicy.Restart, runnerNameSelector: values.runnerNameSelector || "default", }); onClose?.(); @@ -49,7 +51,7 @@ export default function CreateActorDialog({ onClose }: ContentProps) { defaultValues={{ name, key: getRandomKey(), - crashPolicy: CrashPolicy.Restart, + crashPolicy: Rivet.CrashPolicy.Restart, }} > diff --git a/frontend/src/components/actors/form/actor-create-form.tsx b/frontend/src/components/actors/form/actor-create-form.tsx index e82f71a316..8de2b9b95a 100644 --- a/frontend/src/components/actors/form/actor-create-form.tsx +++ b/frontend/src/components/actors/form/actor-create-form.tsx @@ -1,7 +1,5 @@ -import { - useInfiniteQuery, - useSuspenseInfiniteQuery, -} from "@tanstack/react-query"; +import { Rivet } from "@rivetkit/engine-api-full"; +import { useSuspenseInfiniteQuery } from "@tanstack/react-query"; import { useEffect, useRef } from "react"; import { type UseFormReturn, useFormContext } from "react-hook-form"; import z from "zod"; @@ -20,9 +18,7 @@ import { AllRunnerSelect } from "../all-runner-select"; import { BuildSelect } from "../build-select"; import { CrashPolicySelect } from "../crash-policy-select"; import { useEngineCompatDataProvider } from "../data-provider"; -import { CrashPolicy as CrashPolicyEnum } from "../queries"; import { RegionSelect } from "../region-select"; -import { ConnectedRunnerSelect } from "../runner-select"; const jsonValid = z.custom( (value) => { @@ -47,7 +43,7 @@ export const formSchema = z datacenter: z.string(), runnerNameSelector: z.string(), - crashPolicy: z.nativeEnum(CrashPolicyEnum), + crashPolicy: z.nativeEnum(Rivet.CrashPolicy), }) .partial({ datacenter: true, runnerNameSelector: true, crashPolicy: true }); @@ -222,7 +218,7 @@ export const PrefillActorName = () => { const { data: name, isSuccess } = useSuspenseInfiniteQuery({ ...useEngineCompatDataProvider().buildsQueryOptions(), - select: (data) => data.pages[0].builds[0].name, + select: (data) => Object.keys(data.pages[0].names)[0], }); const watchedValue = watch("name"); @@ -274,8 +270,9 @@ export const PrefillDatacenter = () => { ...useEngineCompatDataProvider().runnerConfigsQueryOptions(), select: (data) => { return Object.keys( - Object.values(data.pages[0].runnerConfigs)[0].datacenters, - )[0]; + Object.values(data.pages[0].runnerConfigs)[0]?.datacenters || + {}, + )?.[0]; }, }); diff --git a/frontend/src/components/actors/guard-connectable-inspector.tsx b/frontend/src/components/actors/guard-connectable-inspector.tsx index 2ab92bc190..a2d41405e9 100644 --- a/frontend/src/components/actors/guard-connectable-inspector.tsx +++ b/frontend/src/components/actors/guard-connectable-inspector.tsx @@ -6,20 +6,21 @@ import { useQuery, useSuspenseQuery, } from "@tanstack/react-query"; -import { useMatch, useRouteContext } from "@tanstack/react-router"; +import { useMatch, useRouteContext, useSearch } from "@tanstack/react-router"; import { createContext, type ReactNode, useContext, useMemo } from "react"; import { useLocalStorage } from "usehooks-ts"; -import { useInspectorCredentials } from "@/app/credentials-context"; -import { createInspectorActorContext } from "@/queries/actor-inspector"; -import { queryClient } from "@/queries/global"; import { DiscreteCopyButton } from "../copy-area"; import { getConfig } from "../lib/config"; import { ls } from "../lib/utils"; +import { ShimmerLine } from "../shimmer-line"; import { Button } from "../ui/button"; import { useFiltersValue } from "./actor-filters-context"; -import { ActorProvider, useActor } from "./actor-queries-context"; import { Info } from "./actor-state-tab"; import { useDataProvider, useEngineCompatDataProvider } from "./data-provider"; +import { + ActorInspectorProvider as InspectorProvider, + useActorInspector, +} from "./inspector-context"; import type { ActorId } from "./queries"; const InspectorGuardContext = createContext(null); @@ -35,17 +36,19 @@ export function GuardConnectableInspector({ actorId, children, }: GuardConnectableInspectorProps) { - const { data: { destroyedAt, pendingAllocationAt, startedAt } = {} } = - useQuery({ - ...useDataProvider().actorQueryOptions(actorId), - refetchInterval: 1000, - select: (data) => ({ - destroyedAt: data.destroyedAt, - sleepingAt: data.sleepingAt, - pendingAllocationAt: data.pendingAllocationAt, - startedAt: data.startedAt, - }), - }); + const filters = useFiltersValue({ onlyEphemeral: true }); + const { + data: { destroyedAt, pendingAllocationAt, startedAt, sleepingAt } = {}, + } = useQuery({ + ...useDataProvider().actorQueryOptions(actorId), + refetchInterval: 1000, + select: (data) => ({ + destroyedAt: data.destroyTs, + sleepingAt: data.sleepTs, + pendingAllocationAt: data.pendingAllocationTs, + startedAt: data.startTs, + }), + }); if (destroyedAt) { return ( @@ -65,6 +68,34 @@ export function GuardConnectableInspector({ ); } + if (sleepingAt) { + if (filters.wakeOnSelect?.value?.[0] === "1") { + return ( + + + + } + > + {children} + + ); + } + return ( + +

Unavailable for sleeping Actors.

+ + + } + > + {children} +
+ ); + } + return ( {children} @@ -103,36 +134,57 @@ function ActorContextProvider(props: { actorId: ActorId; children: ReactNode; }) { + const { data, isError } = useQuery( + useDataProvider().actorInspectorTokenQueryOptions(props.actorId), + ); + + if (isError || !data) { + return ( + +

Unable to retrieve the Actor's Inspector token.

+

+ Please verify that the Inspector is enabled for your + Actor and that you are using the latest version of + RivetKit. +

+ + } + > + {props.children} +
+ ); + } + return __APP_TYPE__ === "inspector" ? ( - + ) : ( - + ); } function ActorInspectorProvider({ actorId, + inspectorToken, children, }: { actorId: ActorId; + inspectorToken: string; children: ReactNode; }) { - const { credentials } = useInspectorCredentials(); - - if (!credentials?.url || !credentials?.token) { - throw new Error("Missing inspector credentials"); - } - - const actorContext = useMemo(() => { - return createInspectorActorContext({ - ...credentials, - }); - }, [credentials]); + const url = useSearch({ + from: "/_context", + select: (s) => s.u || "https://localhost:6420", + }); return ( - - {children} - + + {children} + ); } @@ -184,7 +236,7 @@ function useActorRunner({ actorId }: { actorId: ActorId }) { shouldThrow: false, }); - if (!match?.params.namespace || !actor.runner) { + if (!match?.params.namespace || !actor.runnerNameSelector) { throw new Error("Actor is missing required fields"); } @@ -194,7 +246,7 @@ function useActorRunner({ actorId }: { actorId: ActorId }) { isSuccess, } = useQuery({ ...useEngineCompatDataProvider().runnerByNameQueryOptions({ - runnerName: actor.runner, + runnerName: actor.runnerNameSelector, }), retryDelay: 10_000, refetchInterval: 1000, @@ -228,43 +280,31 @@ function useEngineToken() { function useActorEngineContext({ actorId }: { actorId: ActorId }) { const { actor, runner, isLoading } = useActorRunner({ actorId }); const engineToken = useEngineToken(); - const provider = useEngineCompatDataProvider(); - const actorContext = useMemo(() => { - return createInspectorActorContext({ + const credentials = useMemo(() => { + return { url: getConfig().apiUrl, - token: async () => { - const runner = await queryClient.fetchQuery( - provider.runnerByNameQueryOptions({ - runnerName: actor?.runner || "", - }), - ); - return (runner?.metadata?.inspectorToken as string) || ""; - }, - engineToken, - }); - }, [ - actorId, - actor?.runner, - provider.runnerByNameQueryOptions, - engineToken, - ]); + token: engineToken, + }; + }, [engineToken]); - return { actorContext, actor, runner, isLoading }; + return { actor, runner, isLoading, credentials }; } function ActorEngineProvider({ actorId, + inspectorToken, children, }: { actorId: ActorId; children: ReactNode; + inspectorToken: string; }) { - const { actorContext, actor } = useActorEngineContext({ + const { credentials, actor } = useActorEngineContext({ actorId, }); - if (!actor.runner) { + if (!actor.runnerNameSelector) { return ( } @@ -275,9 +315,12 @@ function ActorEngineProvider({ } return ( - - {children} - + + {children} + ); } @@ -300,11 +343,11 @@ function NoRunnerInfo({ runner }: { runner: string }) { } function WakeUpActorButton({ actorId }: { actorId: ActorId }) { - const actorContext = useActor(); + const actorInspector = useActorInspector(); const { runner } = useActorRunner({ actorId }); const { mutate, isPending } = useMutation( - actorContext.actorWakeUpMutationOptions(actorId), + actorInspector.actorWakeUpMutationOptions(actorId), ); if (!runner) return null; return ( @@ -321,21 +364,22 @@ function WakeUpActorButton({ actorId }: { actorId: ActorId }) { } function AutoWakeUpActor({ actorId }: { actorId: ActorId }) { - const actorContext = useActor(); + const actorInspector = useActorInspector(); const { actor, runner } = useActorRunner({ actorId }); - const { hasRunner } = useRunner(actor.runner); + const { hasRunner } = useRunner(actor.runnerNameSelector); useQuery( - actorContext.actorAutoWakeUpQueryOptions(actorId, { + actorInspector.actorAutoWakeUpQueryOptions(actorId, { enabled: hasRunner, }), ); - if (!hasRunner) return ; + if (!hasRunner) + return ; if (runner?.drainTs) - return ; + return ; return ( @@ -347,71 +391,9 @@ function AutoWakeUpActor({ actorId }: { actorId: ActorId }) { ); } -function InspectorGuard({ - actorId, - children, -}: { - actorId: ActorId; - children: ReactNode; -}) { - const filters = useFiltersValue({ onlyEphemeral: true }); - - const { data: { sleepingAt } = {} } = useQuery({ - ...useDataProvider().actorQueryOptions(actorId), - refetchInterval: 1000, - select: (data) => ({ - destroyedAt: data.destroyedAt, - sleepingAt: data.sleepingAt, - pendingAllocationAt: data.pendingAllocationAt, - startedAt: data.startedAt, - }), - }); - - if (sleepingAt) { - if (filters.wakeOnSelect?.value?.[0] === "1") { - return ( - - - - } - > - {children} - - ); - } - return ( - -

Unavailable for sleeping Actors.

- - - } - > - {children} -
- ); - } - return ( - {children} - ); -} - -function InspectorGuardInner({ - actorId, - children, -}: { - actorId: ActorId; - children: ReactNode; -}) { - const { isError } = useQuery({ - ...useActor().actorPingQueryOptions(actorId), - retryDelay: 10_000, - enabled: true, - }); - if (isError) { +function InspectorGuard({ children }: { children: ReactNode }) { + const { connectionStatus } = useActorInspector(); + if (connectionStatus === "error") { return ( + {connectionStatus !== "connected" && } + {children} + + ); } diff --git a/frontend/src/components/actors/index.ts b/frontend/src/components/actors/index.ts index bdf87a9973..4f39184f87 100644 --- a/frontend/src/components/actors/index.ts +++ b/frontend/src/components/actors/index.ts @@ -1,6 +1,4 @@ -export * from "./actor-context"; export * from "./actor-not-found"; -export * from "./actor-queries-context"; export * from "./actor-region"; export * from "./actor-status-indicator"; export * from "./actor-status-label"; diff --git a/frontend/src/components/actors/inspector-context.tsx b/frontend/src/components/actors/inspector-context.tsx new file mode 100644 index 0000000000..cce200a769 --- /dev/null +++ b/frontend/src/components/actors/inspector-context.tsx @@ -0,0 +1,540 @@ +import { + mutationOptions, + type QueryClient, + queryOptions, + useQueryClient, +} from "@tanstack/react-query"; +import * as cbor from "cbor-x"; +import { createContext, useContext, useMemo, useRef } from "react"; +import type ReconnectingWebSocket from "reconnectingwebsocket"; +import { + type Connection, + type Event, + type ToServer, + TO_CLIENT_VERSIONED as toClient, + TO_SERVER_VERSIONED as toServer, +} from "rivetkit/inspector"; +import { toast } from "sonner"; +import { match } from "ts-pattern"; +import { type ConnectionStatus, useWebSocket } from "../hooks/use-websocket"; +import type { ActorId } from "./queries"; + +export const actorInspectorQueriesKeys = { + actorState: (actorId: ActorId) => ["actor", actorId, "state"] as const, + actorIsStateEnabled: (actorId: ActorId) => + ["actor", actorId, "is-state-enabled"] as const, + actorConnections: (actorId: ActorId) => + ["actor", actorId, "connections"] as const, + actorDatabase: (actorId: ActorId) => + ["actor", actorId, "database"] as const, + actorEvents: (actorId: ActorId) => ["actor", actorId, "events"] as const, + actorRpcs: (actorId: ActorId) => ["actor", actorId, "rpcs"] as const, + actorClearEvents: (actorId: ActorId) => + ["actor", actorId, "clear-events"] as const, + actorWakeUp: (actorId: ActorId) => ["actor", actorId, "wake-up"] as const, + actorAutoWakeUp: (actorId: ActorId) => + ["actor", actorId, "auto-wake-up"] as const, +}; + +interface ActorInspectorApi { + ping: () => Promise; + executeAction: (name: string, args: unknown[]) => Promise; + patchState: (state: unknown) => Promise; + getConnections: () => Promise; + getEvents: () => Promise; + getState: () => Promise; +} + +export const createDefaultActorInspectorContext = ({ + api, +}: { + api: ActorInspectorApi; +}) => ({ + api, + actorStateQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorState(actorId), + queryFn: () => { + return api.getState(); + }, + }); + }, + + actorIsStateEnabledQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorIsStateEnabled(actorId), + queryFn: () => { + return false; + }, + }); + }, + + actorConnectionsQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorConnections(actorId), + queryFn: () => { + return api.getConnections(); + }, + }); + }, + + actorDatabaseQueryOptions(actorId: ActorId) { + // TODO: implement + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorDatabase(actorId), + queryFn: () => { + return { enabled: false, db: [] } as unknown as { + enabled: boolean; + db: { + table: { name: string; type: string }; + records: number; + }[]; + }; + }, + }); + }, + + actorDatabaseEnabledQueryOptions(actorId: ActorId) { + // TODO: implement + return queryOptions({ + staleTime: Infinity, + ...this.actorDatabaseQueryOptions(actorId), + select: (data) => data.enabled, + }); + }, + + actorDatabaseTablesQueryOptions(actorId: ActorId) { + // TODO: implement + return queryOptions({ + ...this.actorDatabaseQueryOptions(actorId), + select: (data) => + data.db?.map((table) => ({ + name: table.table.name, + type: table.table.type, + records: table.records, + })) || [], + notifyOnChangeProps: ["data", "isError", "isLoading"], + }); + }, + + actorDatabaseRowsQueryOptions(actorId: ActorId, table: string) { + // TODO: implement + return queryOptions({ + staleTime: Infinity, + queryKey: [ + ...actorInspectorQueriesKeys.actorDatabase(actorId), + table, + ], + queryFn: () => { + return [] as unknown as Record[]; + }, + }); + }, + + actorEventsQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorEvents(actorId), + queryFn: () => { + return api.getEvents(); + }, + }); + }, + + actorRpcsQueryOptions(actorId: ActorId) { + return queryOptions({ + staleTime: Infinity, + queryKey: actorInspectorQueriesKeys.actorRpcs(actorId), + queryFn: () => { + return [] as string[]; + }, + }); + }, + + actorClearEventsMutationOptions(actorId: ActorId) { + return mutationOptions({ + mutationKey: ["actor", actorId, "clear-events"], + // TODO: + }); + }, + + actorWakeUpMutationOptions(actorId: ActorId) { + return mutationOptions({ + mutationKey: ["actor", actorId, "wake-up"], + // TODO: + }); + }, + + actorAutoWakeUpQueryOptions( + actorId: ActorId, + { enabled }: { enabled?: boolean } = {}, + ) { + return queryOptions({ + enabled, + refetchInterval: 1000, + staleTime: 0, + gcTime: 0, + queryKey: actorInspectorQueriesKeys.actorAutoWakeUp(actorId), + retry: false, + //FIXME: + }); + }, + + actorPingQueryOptions(actorId: ActorId) { + return queryOptions({ + queryKey: ["actor", actorId, "ping"], + queryFn: async () => { + try { + await api.ping(); + return true; + } catch { + return false; + } + }, + retry: false, + }); + }, + + actorStatePatchMutation(actorId: ActorId) { + return mutationOptions({ + mutationKey: ["actor", actorId, "state", "patch"], + mutationFn: async (state: unknown) => { + return api.patchState(state); + }, + }); + }, +}); + +export type ActorInspectorContext = ReturnType< + typeof createDefaultActorInspectorContext +> & { connectionStatus: ConnectionStatus }; + +const ActorInspectorContext = createContext({} as ActorInspectorContext); + +export const useActorInspector = () => useContext(ActorInspectorContext); + +export const ActorInspectorProvider = ({ + children, + actorId, + credentials, +}: { + children: React.ReactNode; + actorId: ActorId; + credentials: { url: string; inspectorToken: string; token: string }; +}) => { + const protocols = useMemo( + () => + [ + "rivet", + `rivet_target.actor`, + `rivet_actor.${actorId}`, + `rivet_encoding.bare`, + credentials.token ? `rivet_token.${credentials.token}` : "", + credentials.inspectorToken + ? `rivet_inspector_token.${credentials.inspectorToken}` + : "", + ].filter(Boolean), + [actorId, credentials.token, credentials.inspectorToken], + ); + + const queryClient = useQueryClient(); + + const actionsManager = useRef(new ActionsManager()); + + const onMessage = useMemo(() => { + return createMessageHandler({ queryClient, actorId, actionsManager }); + }, [queryClient, actorId]); + + const { sendMessage, reconnect, status } = useWebSocket( + new URL(`/gateway/${actorId}/inspector/connect`, credentials.url).href, + protocols, + { onMessage }, + ); + + const api = useMemo(() => { + return { + ping: async () => { + return reconnect(); + }, + executeAction: async (name, args) => { + const { id, promise } = + actionsManager.current.createSuspension(); + + sendMessage( + serverMessage({ + body: { + tag: "ActionRequest", + val: { + id: BigInt(id), + name, + args: new Uint8Array(cbor.encode(args)).buffer, + }, + }, + }), + ); + + return promise; + }, + + patchState: async (state) => { + sendMessage( + serverMessage({ + body: { + tag: "PatchStateRequest", + val: { + state: new Uint8Array(cbor.encode(state)) + .buffer, + }, + }, + }), + ); + }, + + getConnections: async () => { + const { id, promise } = + actionsManager.current.createSuspension(); + + sendMessage( + serverMessage({ + body: { + tag: "ConnectionsRequest", + val: { id: BigInt(id) }, + }, + }), + ); + + return promise; + }, + + getEvents: async () => { + const { id, promise } = + actionsManager.current.createSuspension(); + + sendMessage( + serverMessage({ + body: { + tag: "EventsRequest", + val: { id: BigInt(id) }, + }, + }), + ); + + return promise; + }, + + getState: async () => { + const { id, promise } = + actionsManager.current.createSuspension(); + + sendMessage( + serverMessage({ + body: { + tag: "StateRequest", + val: { id: BigInt(id) }, + }, + }), + ); + + return promise; + }, + } satisfies ActorInspectorApi; + }, [sendMessage, reconnect]); + + const value = useMemo(() => { + return { + connectionStatus: status, + ...createDefaultActorInspectorContext({ + api, + }), + }; + }, [api, status]); + + return ( + + {children} + + ); +}; + +const createMessageHandler = + ({ + queryClient, + actorId, + actionsManager, + }: { + queryClient: QueryClient; + actorId: ActorId; + actionsManager: React.RefObject; + }) => + async (e: ReconnectingWebSocket.MessageEvent) => { + const message = toClient.deserializeWithEmbeddedVersion( + new Uint8Array(await e.data.arrayBuffer()), + ); + + match(message.body) + .with({ tag: "Init" }, (body) => { + queryClient.setQueryData( + actorInspectorQueriesKeys.actorState(actorId), + transformState(body.val.state), + ); + + queryClient.setQueryData( + actorInspectorQueriesKeys.actorConnections(actorId), + transformConnections(body.val.connections), + ); + + queryClient.setQueryData( + actorInspectorQueriesKeys.actorEvents(actorId), + transformEvents(body.val.events), + ); + + queryClient.setQueryData( + actorInspectorQueriesKeys.actorIsStateEnabled(actorId), + body.val.isStateEnabled, + ); + }) + .with({ tag: "ConnectionsResponse" }, (body) => { + const { rid } = body.val; + actionsManager.current.resolveSuspension( + Number(rid), + transformConnections(body.val.connections), + ); + }) + .with({ tag: "EventsResponse" }, (body) => { + const { rid } = body.val; + actionsManager.current.resolveSuspension( + Number(rid), + transformEvents(body.val.events), + ); + }) + .with({ tag: "StateResponse" }, (body) => { + const { rid } = body.val; + actionsManager.current.resolveSuspension( + Number(rid), + cbor.decode(new Uint8Array(body.val.state)), + ); + }) + .with({ tag: "ActionResponse" }, (body) => { + const { rid } = body.val; + actionsManager.current.resolveSuspension( + Number(rid), + cbor.decode(new Uint8Array(body.val.output)), + ); + }) + .with({ tag: "ConnectionsUpdated" }, (body) => { + queryClient.setQueryData( + actorInspectorQueriesKeys.actorConnections(actorId), + transformConnections(body.val.connections), + ); + }) + .with({ tag: "StateUpdated" }, (body) => { + queryClient.setQueryData( + actorInspectorQueriesKeys.actorState(actorId), + transformState(body.val.state), + ); + }) + .with({ tag: "EventsUpdated" }, (body) => { + queryClient.setQueryData( + actorInspectorQueriesKeys.actorEvents(actorId), + transformEvents(body.val.events), + ); + }) + .with({ tag: "Error" }, (body) => { + toast.error(`Inspector error: ${body.val.message}`); + }) + .exhaustive(); + }; + +function transformEvents(events: readonly Event[]) { + return events.map((event) => { + const base = { + ...event, + timestamp: new Date(Number(event.timestamp)), + }; + + return match(event.body) + .with({ tag: "FiredEvent" }, (body) => ({ + ...base, + body: { + ...body, + val: { + ...body.val, + args: cbor.decode(new Uint8Array(body.val.args)), + }, + }, + })) + .with({ tag: "ActionEvent" }, (body) => ({ + ...base, + body: { + ...body, + val: { + ...body.val, + args: cbor.decode(new Uint8Array(body.val.args)), + }, + }, + })) + .with({ tag: "BroadcastEvent" }, (body) => ({ + ...base, + body: { + ...body, + val: { + ...body.val, + args: cbor.decode(new Uint8Array(body.val.args)), + }, + }, + })) + .with({ tag: "SubscribeEvent" }, (body) => ({ + ...base, + body: { + ...body, + }, + })) + .with({ tag: "UnSubscribeEvent" }, (body) => ({ + ...base, + body: { + ...body, + }, + })) + .exhaustive(); + }); +} + +function transformConnections(connections: readonly Connection[]) { + return connections.map((connection) => ({ + ...connection, + details: cbor.decode(new Uint8Array(connection.details)), + })); +} + +function transformState(state: ArrayBuffer) { + return cbor.decode(new Uint8Array(state)); +} + +function serverMessage(data: ToServer) { + return toServer.serializeWithEmbeddedVersion(data); +} + +class ActionsManager { + private suspensions = new Map>(); + + private nextId = 1; + + createSuspension(): { id: number; promise: Promise } { + const id = this.nextId++; + const { promise, resolve, reject } = Promise.withResolvers(); + this.suspensions.set(id, { promise, resolve, reject }); + return { id, promise }; + } + + resolveSuspension(id: number, value?: unknown) { + const suspension = this.suspensions.get(id); + if (suspension) { + suspension.resolve(value); + this.suspensions.delete(id); + } + } +} diff --git a/frontend/src/components/actors/queries/actor.ts b/frontend/src/components/actors/queries/actor.ts deleted file mode 100644 index d372d9a996..0000000000 --- a/frontend/src/components/actors/queries/actor.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { fetchEventSource } from "@microsoft/fetch-event-source"; -import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { compare } from "fast-json-patch"; -import { useCallback, useEffect } from "react"; -import type { ActorId, RecordedRealtimeEvent } from "rivetkit/inspector"; -import { useAsyncMemo } from "@/components/hooks/use-async-memo"; -import { useActor } from "../actor-queries-context"; - -export const useActorClearEventsMutation = ( - actorId: ActorId, - options?: Parameters[1], -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - return useMutation({ - ...queries.actorClearEventsMutationOptions(actorId), - onMutate: async () => { - queryClient.setQueryData( - queries.actorEventsQueryOptions(actorId).queryKey, - () => ({ events: [] }), - ); - }, - ...options, - }); -}; - -export const useActorStatePatchMutation = ( - actorId: ActorId, - options?: Parameters[1], -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - return useMutation({ - mutationFn: async (data: any) => { - const client = await queries.createActorInspector(actorId); - - const oldStateQuery = queryClient.getQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - ); - - const oldState = oldStateQuery?.state; - - let response: Awaited>; - - if (!oldState || !isPatchable(data)) { - response = await client.state.$patch({ - // its okay, we know the type - json: { replace: data }, - }); - } else { - const patches = compare(oldState, data); - response = await client.state.$patch({ - // its okay, we know the type - // @ts-expect-error - json: { patch: patches }, - }); - } - - if (!response.ok) { - throw response; - } - return (await response.json()) as { - state: unknown; - enabled: boolean; - }; - }, - onSuccess: (data) => { - queryClient.setQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - () => ({ enabled: true, state: data.state }), - ); - }, - ...options, - }); -}; - -const getHeaders = ( - v: - | Record - | (() => Record | Promise>), -) => { - if (typeof v === "function") { - return v(); - } - return v; -}; - -function useStream( - actorId: ActorId, - onMessage: (data: T) => void, - url: string | null | undefined, - opts: { enabled: boolean } = { enabled: true }, -) { - const stableOnMessage = useCallback(onMessage, []); - const queries = useActor(); - - useEffect(() => { - const controller = new AbortController(); - if (!opts.enabled || !url) { - controller.abort(); - return () => controller.abort(); - } - - async function establishConnection() { - if (!url) { - return; - } - fetchEventSource(url, { - signal: controller.signal, - headers: await getHeaders( - ( - await queries.createActorInspectorFetchConfiguration( - actorId, - ) - )?.headers || {}, - ), - onmessage: (event) => { - const msg = JSON.parse(event.data); - stableOnMessage(msg); - }, - onclose: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - controller.signal.throwIfAborted(); - establishConnection(); - }, - }).catch((error) => console.error(error)); - } - - establishConnection(); - return () => { - controller.abort(); - }; - }, [url, actorId, opts.enabled, stableOnMessage]); -} - -export const useActorStateStream = ( - actorId: ActorId, - opts: { enabled: boolean } = { enabled: true }, -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data: unknown) => { - queryClient.setQueryData( - queries.actorStateQueryOptions(actorId).queryKey, - () => ({ enabled: true, state: data }), - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).state.stream.$url().href, - [actorId, queries], - ), - opts, - ); -}; - -export const useActorConnectionsStream = (actorId: ActorId) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data) => { - queryClient.setQueryData( - queries.actorConnectionsQueryOptions(actorId).queryKey, - () => ({ enabled: true, connections: data }), - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).connections.stream.$url().href, - [actorId, queries], - ), - ); -}; - -export const useActorEventsStream = ( - actorId: ActorId, - opts: { enabled: boolean }, -) => { - const queryClient = useQueryClient(); - const queries = useActor(); - - useStream( - actorId, - useCallback( - (data: RecordedRealtimeEvent[]) => { - queryClient.setQueryData( - queries.actorEventsQueryOptions(actorId).queryKey, - () => { - return { events: data }; - }, - ); - }, - [queryClient, actorId, queries], - ), - useAsyncMemo( - async () => - ( - await queries.createActorInspector(actorId) - ).events.stream.$url().href, - [actorId, queries], - ), - opts, - ); -}; - -/** - * Check if the object is patchable, i.e. if it is an object and not null. - */ -function isPatchable(data: unknown) { - return typeof data === "object" && data !== null; -} diff --git a/frontend/src/components/actors/queries/index.ts b/frontend/src/components/actors/queries/index.ts index 383c469228..47f1d4f6df 100644 --- a/frontend/src/components/actors/queries/index.ts +++ b/frontend/src/components/actors/queries/index.ts @@ -1,95 +1,6 @@ -import type { Actor as InspectorActor } from "rivetkit/inspector"; +import type { Rivet } from "@rivetkit/engine-api-full"; -export type { ActorLogEntry } from "rivetkit/inspector"; -export { ActorFeature } from "rivetkit/inspector"; - -import type { ActorId } from "rivetkit/inspector"; - -export type { ActorId }; - -export type PortRouting = { - guard?: {}; - host?: {}; -}; - -export type Port = { - protocol: "http" | "https" | "tcp" | "tcp_tls" | "udp"; - internalPort?: number; - hostname?: string; - port?: number; - path?: string; - /** Fully formed connection URL including protocol, hostname, port, and path, if applicable. */ - url?: string; - routing: PortRouting; -}; - -export type Runtime = { - build: string; - arguments?: string[]; - environment?: Record; -}; - -export type Lifecycle = { - /** The duration to wait for in milliseconds before killing the actor. This should be set to a safe default, and can be overridden during a DELETE request if needed. */ - killTimeout?: number; - /** If true, the actor will try to reschedule itself automatically in the event of a crash or a datacenter failover. The actor will not reschedule if it exits successfully. */ - durable?: boolean; -}; - -export type Resources = { - /** - * The number of CPU cores in millicores, or 1/1000 of a core. For example, - * 1/8 of a core would be 125 millicores, and 1 core would be 1000 - * millicores. - */ - cpu: number; - /** The amount of memory in megabytes */ - memory: number; -}; - -export type Actor = Omit & { - network?: { - mode: "bridge" | "host"; - ports: Record; - }; - runtime?: Runtime; - lifecycle?: Lifecycle; - key: string | undefined; - - // engine related - runner?: string; - crashPolicy?: CrashPolicy; - sleepingAt?: string | null; - connectableAt?: string | null; - pendingAllocationAt?: string | null; - datacenter?: string | null; - rescheduleAt?: string | null; -} & { id: ActorId }; - -export enum CrashPolicy { - Restart = "restart", - Sleep = "sleep", - Destroy = "destroy", -} - -export type ActorMetrics = { - metrics: Record; - rawData: Record; - interval: number; -}; - -export type Build = { - id: string; - name: string; -}; - -export type Region = { - id: string; - name: string; - url?: string; -}; - -export * from "./actor"; +export type ActorId = string; export type ActorStatus = | "starting" @@ -103,49 +14,49 @@ export type ActorStatus = export function getActorStatus( actor: Pick< - Actor, - | "createdAt" - | "startedAt" - | "destroyedAt" - | "sleepingAt" - | "pendingAllocationAt" - | "rescheduleAt" + Rivet.Actor, + | "createTs" + | "destroyTs" + | "sleepTs" + | "pendingAllocationTs" + | "rescheduleTs" + | "connectableTs" >, ): ActorStatus { const { - createdAt, - startedAt, - destroyedAt, - sleepingAt, - pendingAllocationAt, - rescheduleAt, + createTs, + connectableTs, + destroyTs, + sleepTs, + pendingAllocationTs, + rescheduleTs, } = actor; - if (rescheduleAt) { + if (rescheduleTs) { return "crash-loop"; } - if (pendingAllocationAt && !startedAt && !destroyedAt) { + if (pendingAllocationTs && !connectableTs && !destroyTs) { return "pending"; } - if (createdAt && sleepingAt && !destroyedAt) { + if (createTs && sleepTs && !destroyTs) { return "sleeping"; } - if (createdAt && !startedAt && !destroyedAt) { + if (createTs && !connectableTs && !destroyTs) { return "starting"; } - if (createdAt && startedAt && !destroyedAt) { + if (createTs && connectableTs && !destroyTs) { return "running"; } - if (createdAt && startedAt && destroyedAt) { + if (createTs && connectableTs && destroyTs) { return "stopped"; } - if (createdAt && !startedAt && destroyedAt) { + if (createTs && !connectableTs && destroyTs) { return "crashed"; } diff --git a/frontend/src/components/actors/region-select.tsx b/frontend/src/components/actors/region-select.tsx index bb8dee1831..40cc2ee55e 100644 --- a/frontend/src/components/actors/region-select.tsx +++ b/frontend/src/components/actors/region-select.tsx @@ -19,7 +19,7 @@ export function RegionSelect({ fetchNextPage, isLoading, isFetchingNextPage, - } = useInfiniteQuery(useDataProvider().regionsQueryOptions()); + } = useInfiniteQuery(useDataProvider().datacentersQueryOptions()); const regions = [ ...(showAuto @@ -33,8 +33,8 @@ export function RegionSelect({ : []), ...data.map((region) => { return { - label: , - value: region.id, + label: , + value: region.name, region, }; }), @@ -50,10 +50,7 @@ export function RegionSelect({ onLoadMore={fetchNextPage} filter={(option, searchMixed) => { const search = searchMixed.toLowerCase(); - return ( - option.region.id.includes(search) || - option.region.name.includes(search) - ); + return option.region.name.toLowerCase().includes(search); }} className="w-full" /> diff --git a/frontend/src/components/actors/worker/actor-repl.worker.ts b/frontend/src/components/actors/worker/actor-repl.worker.ts index 51d6049937..e946342dc1 100644 --- a/frontend/src/components/actors/worker/actor-repl.worker.ts +++ b/frontend/src/components/actors/worker/actor-repl.worker.ts @@ -1,12 +1,10 @@ import { fromJs } from "esast-util-from-js"; import { toJs } from "estree-util-to-js"; -import { createActorInspectorClient } from "rivetkit/inspector"; import { createHighlighterCore, createOnigurumaEngine, type HighlighterCore, } from "shiki"; -import { match } from "ts-pattern"; import { type InitMessage, MessageSchema, @@ -104,6 +102,9 @@ const createConsole = (id: string) => { }; let init: null | Omit = null; +let actionIdCounter = 0; + +const actions = new Map>(); addEventListener("message", async (event) => { const { success, error, data } = MessageSchema.safeParse(event.data); @@ -121,6 +122,21 @@ addEventListener("message", async (event) => { return; } + if (data.type === "actionResponse") { + const action = actions.get(data.id); + if (!action) { + console.error("No such action", data.id); + return; + } + if (data.data.success) { + action.resolve(data.data.result); + } else { + action.reject(new Error(data.data.error || "Unknown error")); + } + actions.delete(data.id); + return; + } + if (data.type === "code") { const actor = init; if (!actor) { @@ -177,44 +193,21 @@ function respond(msg: Response) { async function callAction({ name, args }: { name: string; args: unknown[] }) { if (!init) throw new Error("Actor not initialized"); - const url = new URL(`inspect`, init.endpoint).href; - - const additionalHeaders = match(__APP_TYPE__) - .with("engine", () => { - return init?.engineToken - ? { "X-Rivet-Token": init.engineToken || "" } - : ({} as Record); - }) - .otherwise(() => ({})); - - // we need to build this from scratch because we don't have access to - // createInspectorActorContext in the worker - // and we want to avoid bundling the entire RivetKit here, issues with @react-refresh - const client = createActorInspectorClient(url, { - headers: { - Authorization: init.inspectorToken - ? `Bearer ${init.inspectorToken}` - : "", - "x-rivet-target": "actor", - "x-rivet-actor": init.id, - "X-RivetKit-Query": JSON.stringify({ - getForId: { actorId: init.id }, - }), - ...additionalHeaders, - }, - }); + const actionId = actionIdCounter++; - const response = await client.action.$post({ - json: { name, params: args }, + const { promise, resolve, reject } = Promise.withResolvers(); + actions.set(actionId, { promise, resolve, reject }); + + respond({ + type: "invokeAction", + id: actionId, + data: { name, args }, }); - if (!response.ok) { - try { - return await response.json(); - } catch { - return await response.text(); - } + try { + const result = await promise; + return result; + } finally { + actions.delete(actionId); } - - return (await response.json()).result; } diff --git a/frontend/src/components/actors/worker/actor-worker-container.ts b/frontend/src/components/actors/worker/actor-worker-container.ts index 3a1a580eb8..b46adae760 100644 --- a/frontend/src/components/actors/worker/actor-worker-container.ts +++ b/frontend/src/components/actors/worker/actor-worker-container.ts @@ -1,6 +1,7 @@ import { CancelledError } from "@tanstack/react-query"; import ActorWorker from "./actor-repl.worker?worker"; import { + type ActionResponse, type CodeMessage, type FormattedCode, type InitMessage, @@ -41,7 +42,8 @@ interface Meta { engineToken?: string | (() => Promise) | (() => string); runnerName?: string; namespace?: string; - inspectorToken?: string; + inspectorToken?: string | null; + invokeAction?: (action: string, params: unknown[]) => Promise; } export class ActorWorkerContainer { @@ -70,11 +72,6 @@ export class ActorWorkerContainer { try { signal.throwIfAborted(); - // FIXME(RVT-4553) - // if (actor.resources.cpu !== 125 || actor.resources.memory !== 128) { - // throw new Error("Unsupported actor resources"); - // } - // If we reached this point, the actor is supported // check if we still operate on the same actor if (this.#meta.actorId !== meta.actorId) { @@ -263,6 +260,25 @@ export class ActorWorkerContainer { } } + if (msg.type === "invokeAction") { + this.#meta + ?.invokeAction?.(msg.data.name, msg.data.args) + .then((result) => { + this.#worker?.postMessage({ + type: "actionResponse", + id: msg.id, + data: { success: true, result }, + } satisfies ActionResponse); + }) + .catch((error) => { + this.#worker?.postMessage({ + type: "actionResponse", + id: msg.id, + data: { success: false, error: String(error) }, + } satisfies ActionResponse); + }); + } + if (msg.type === "ready") { this.#state.status = { type: "ready" }; this.#update(); diff --git a/frontend/src/components/actors/worker/actor-worker-context.tsx b/frontend/src/components/actors/worker/actor-worker-context.tsx index 4288f49c8a..22b9fb8de7 100644 --- a/frontend/src/components/actors/worker/actor-worker-context.tsx +++ b/frontend/src/components/actors/worker/actor-worker-context.tsx @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/correctness/useHookAtTopLevel: guarded by build constant */ import { useQuery } from "@tanstack/react-query"; import { createContext, @@ -9,11 +10,10 @@ import { useSyncExternalStore, } from "react"; import { match } from "ts-pattern"; -import { useInspectorCredentials } from "@/app/credentials-context"; -import { assertNonNullable, ls } from "../../lib/utils"; -import { useActor } from "../actor-queries-context"; +import { assertNonNullable } from "../../lib/utils"; import { useDataProvider, useEngineCompatDataProvider } from "../data-provider"; -import { ActorFeature, type ActorId } from "../queries"; +import { useActorInspector } from "../inspector-context"; +import type { ActorId } from "../queries"; import { ActorWorkerContainer } from "./actor-worker-container"; export const ActorWorkerContext = createContext( @@ -26,23 +26,6 @@ export const useActorWorker = () => { return value; }; -const useInspectorToken = (runnerName: string) => { - return match(__APP_TYPE__) - .with("inspector", () => { - return useInspectorCredentials().credentials?.token; - }) - .otherwise(() => { - const provider = useEngineCompatDataProvider(); - const { data } = useQuery( - provider.runnerByNameQueryOptions({ - runnerName, - }), - ); - - return (data?.metadata?.inspectorToken as string) || ""; - }); -}; - const useConnectionDetails = () => { return match(__APP_TYPE__) .with("inspector", () => { @@ -70,7 +53,6 @@ export const ActorWorkerContextProvider = ({ const { data: { - features, name, endpoint, destroyedAt, @@ -79,19 +61,17 @@ export const ActorWorkerContextProvider = ({ runner, } = {}, } = useQuery(dataProvider.actorWorkerQueryOptions(actorId)); - const inspectorToken = useInspectorToken(runner || ""); + const { data: inspectorToken } = useQuery( + useDataProvider().actorInspectorTokenQueryOptions(actorId), + ); - const enabled = - (features?.includes(ActorFeature.Console) && - !destroyedAt && - !sleepingAt && - !!startedAt) ?? - false; + const enabled = (!destroyedAt && !sleepingAt && !!startedAt) ?? false; - const actorQueries = useActor(); - const { data: { rpcs } = {} } = useQuery( - actorQueries.actorRpcsQueryOptions(actorId, { enabled }), - ); + const actorInspector = useActorInspector(); + const { data: rpcs = [] } = useQuery({ + ...actorInspector.actorRpcsQueryOptions(actorId), + enabled, + }); const [container] = useState( () => new ActorWorkerContainer(), @@ -112,6 +92,7 @@ export const ActorWorkerContextProvider = ({ runnerName: runner, namespace, inspectorToken, + invokeAction: actorInspector.api.executeAction, }); } @@ -129,6 +110,7 @@ export const ActorWorkerContextProvider = ({ inspectorToken, namespace, runner, + actorInspector.api.executeAction, ]); return ( diff --git a/frontend/src/components/actors/worker/actor-worker-schema.ts b/frontend/src/components/actors/worker/actor-worker-schema.ts index a90aee8ad0..1226b9d93e 100644 --- a/frontend/src/components/actors/worker/actor-worker-schema.ts +++ b/frontend/src/components/actors/worker/actor-worker-schema.ts @@ -18,12 +18,23 @@ const InitMessageSchema = z.object({ engineToken: z.string().optional(), namespace: z.string().optional(), runnerName: z.string().optional(), - inspectorToken: z.string().optional(), + inspectorToken: z.string().nullish(), +}); + +const ActionResponseSchema = z.object({ + type: z.literal("actionResponse"), + id: z.number(), + data: z.object({ + result: z.any().optional(), + error: z.string().optional(), + success: z.boolean(), + }), }); export const MessageSchema = z.discriminatedUnion("type", [ CodeMessageSchema, InitMessageSchema, + ActionResponseSchema, ]); export const FormattedCodeSchema = z @@ -70,6 +81,14 @@ export const ResponseSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("ready"), }), + z.object({ + type: z.literal("invokeAction"), + id: z.number(), + data: z.object({ + name: z.string(), + args: z.array(z.any()), + }), + }), ]); export type Response = z.infer; @@ -78,3 +97,4 @@ export type FormattedCode = z.infer; export type Log = z.infer; export type InitMessage = z.infer; export type CodeMessage = z.infer; +export type ActionResponse = z.infer; diff --git a/frontend/src/components/connection-form.tsx b/frontend/src/components/connection-form.tsx index 4c9fb60019..cc1e00197a 100644 --- a/frontend/src/components/connection-form.tsx +++ b/frontend/src/components/connection-form.tsx @@ -1,8 +1,13 @@ import type { ComponentProps } from "react"; import z from "zod"; import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, Button, createSchemaForm, + FormDescription, FormField, FormItem, FormLabel, @@ -11,11 +16,7 @@ import { } from "@/components"; const connectionFormSchema = z.object({ - username: z - .string() - .url("Please enter a valid URL") - .min(1, "URL is required"), - token: z.string().min(1, "Token is required"), + url: z.string().url("Please enter a valid URL").min(1, "URL is required"), }); const { Form, Submit: ConnectionSubmit } = @@ -28,7 +29,7 @@ export const ConnectionForm = (
( Endpoint @@ -41,20 +42,35 @@ export const ConnectionForm = ( )} /> - ( - - Token - + + + Advanced + + + + ( + + Token + + + Connecting to Rivet Engine? You will + need to provide an access token + here. + + + + )} /> - - - )} - /> + + + */}