forked from cerc-io/snowballtools-base
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0294b3729d | ||
|
|
216a5670e6 | ||
|
|
92016c8837 | ||
|
|
c6ebcafaac | ||
|
|
6dfe85cb1a | ||
|
|
2bb5feeb88 | ||
|
|
9a74205bd5 | ||
|
|
7147611842 | ||
|
|
8533c41c58 | ||
|
|
2074e08a0c | ||
|
|
f01bdf2de7 | ||
|
|
8cb5eadfb2 | ||
|
|
41666568f5 | ||
|
|
17cf878168 | ||
|
|
c72cbce615 | ||
|
|
a69dd71117 | ||
|
|
5dc4d28b50 | ||
|
|
7dce1d66ae | ||
|
|
46ba6d014d | ||
|
|
8b39f664ad | ||
|
|
5f4be30799 | ||
|
|
b53e12b94b | ||
|
|
f290b5c0b5 | ||
|
|
4fa6f418ba | ||
|
|
328da7fdc8 | ||
|
|
8210512eea | ||
|
|
cd2dce2404 | ||
|
|
c2158510d9 | ||
|
|
096fd04a22 | ||
|
|
a8d93732ce | ||
|
|
953c3fc10b | ||
|
|
3cbea57294 | ||
|
|
dc6f436409 | ||
|
|
691edf3508 | ||
|
|
fe599f97a3 |
@@ -99,25 +99,13 @@ Let us assume the following domains for backend and frontend
|
||||
- Get the private key and set `registryConfig.privateKey` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```bash
|
||||
laconic-so --stack fixturenet-laconic-loaded deploy exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
|
||||
laconic-so deployment --dir laconic-loaded-deployment exec laconicd "laconicd keys export mykey --unarmored-hex --unsafe"
|
||||
# WARNING: The private key will be exported as an unarmored hexadecimal string. USE AT YOUR OWN RISK. Continue? [y/N]: y
|
||||
# 754cca7b4b729a99d156913aea95366411d072856666e95ba09ef6c664357d81
|
||||
```
|
||||
|
||||
- Get the REST and GQL endpoint ports of Laconicd and replace the ports for `registryConfig.restEndpoint` and `registryConfig.gqlEndpoint` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
```bash
|
||||
# For registryConfig.restEndpoint
|
||||
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 1317
|
||||
# 0.0.0.0:32777
|
||||
|
||||
# For registryConfig.gqlEndpoint
|
||||
laconic-so --stack fixturenet-laconic-loaded deploy port laconicd 9473
|
||||
# 0.0.0.0:32771
|
||||
```
|
||||
|
||||
|
||||
- Set authority in `registryConfig.authority` in backend [config file](packages/backend/environments/local.toml)
|
||||
|
||||
|
||||
- Run the script to create bond, reserve the authority and set authority bond
|
||||
|
||||
```bash
|
||||
@@ -157,14 +145,14 @@ Let us assume the following domains for backend and frontend
|
||||
- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
REACT_APP_GITHUB_CLIENT_ID = <CLIENT_ID>
|
||||
VITE_GITHUB_CLIENT_ID = <CLIENT_ID>
|
||||
```
|
||||
|
||||
- Set `REACT_APP_GITHUB_PWA_TEMPLATE_REPO` and `REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
|
||||
- Set `VITE_GITHUB_PWA_TEMPLATE_REPO` and `VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
|
||||
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
|
||||
VITE_GITHUB_PWA_TEMPLATE_REPO = 'cerc-io/test-progressive-web-app' # Set actual owner/name of the template repo that will be used for creating new repo
|
||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'cerc-io/image-upload-pwa-example' # Set actual owner/name of the template repo that will be used for creating new repo
|
||||
```
|
||||
|
||||
- Production
|
||||
@@ -172,17 +160,17 @@ Let us assume the following domains for backend and frontend
|
||||
- Set the following values in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
REACT_APP_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
|
||||
VITE_SERVER_URL = 'https://api.snowballtools.com' # Backend server endpoint
|
||||
```
|
||||
|
||||
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID
|
||||
- Create a project and add information to use wallet connect SDK
|
||||
- Add project name and select project type as `App`
|
||||
- Set project home page URL to `https://dashboard.snowballtools.com`
|
||||
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
REACT_APP_WALLET_CONNECT_ID = <PROJECT_ID>
|
||||
VITE_WALLET_CONNECT_ID = <PROJECT_ID>
|
||||
```
|
||||
|
||||
- Build the React application
|
||||
@@ -202,17 +190,17 @@ Let us assume the following domains for backend and frontend
|
||||
- Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend`
|
||||
|
||||
```env
|
||||
REACT_APP_SERVER_URL = 'http://localhost:8000'
|
||||
VITE_SERVER_URL = 'http://localhost:8000'
|
||||
```
|
||||
|
||||
- Sign in to [wallet connect](https://cloud.walletconnect.com/sign-in) to create a project ID.
|
||||
- Create a project and add information to use wallet connect SDK
|
||||
- Add project name and select project type as `App`
|
||||
- Project home page URL is not required to be set
|
||||
- On creation of project, use the `Project ID` and set it in `REACT_APP_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
- On creation of project, use the `Project ID` and set it in `VITE_WALLET_CONNECT_ID` in [.env](packages/frontend/.env) file
|
||||
|
||||
```env
|
||||
REACT_APP_WALLET_CONNECT_ID = <Project_ID>
|
||||
VITE_WALLET_CONNECT_ID = <Project_ID>
|
||||
```
|
||||
|
||||
- The React application will be running in `http://localhost:3000/`
|
||||
|
||||
+11
-5
@@ -10,11 +10,17 @@ if [[ -d "$DEST_DIR" ]]; then
|
||||
fi
|
||||
|
||||
cat > $PKG_DIR/.env <<EOF
|
||||
REACT_APP_SERVER_URL = 'LACONIC_HOSTED_CONFIG_app_server_url'
|
||||
REACT_APP_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_app_github_clientid'
|
||||
REACT_APP_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo'
|
||||
REACT_APP_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo'
|
||||
REACT_APP_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_app_wallet_connect_id'
|
||||
VITE_SERVER_URL = 'LACONIC_HOSTED_CONFIG_server_url'
|
||||
VITE_GITHUB_CLIENT_ID = 'LACONIC_HOSTED_CONFIG_github_clientid'
|
||||
VITE_GITHUB_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_pwa_templaterepo'
|
||||
VITE_GITHUB_IMAGE_UPLOAD_PWA_TEMPLATE_REPO = 'LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo'
|
||||
VITE_WALLET_CONNECT_ID = 'LACONIC_HOSTED_CONFIG_wallet_connect_id'
|
||||
VITE_LIT_RELAY_API_KEY = 'LACONIC_HOSTED_CONFIG_lit_relay_api_key'
|
||||
VITE_ALCHEMY_API_KEY = 'LACONIC_HOSTED_CONFIG_aplchemy_api_key'
|
||||
VITE_BUGSNAG_API_KEY = 'LACONIC_HOSTED_CONFIG_bugsnag_api_key'
|
||||
VITE_PASSKEY_WALLET_RPID = 'LACONIC_HOSTED_CONFIG_passkey_wallet_rpid'
|
||||
VITE_TURNKEY_API_BASE_URL = 'LACONIC_HOSTED_CONFIG_turnkey_api_base_url'
|
||||
VITE_TURNKEY_ORGANIZATION_ID = 'LACONIC_HOSTED_CONFIG_turnkey_organization_id'
|
||||
EOF
|
||||
|
||||
yarn || exit 1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"license": "UNLICENSED",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
@@ -43,6 +44,7 @@
|
||||
"lint": "tsc --noEmit",
|
||||
"test:registry:init": "DEBUG=snowball:* ts-node ./test/initialize-registry.ts",
|
||||
"test:registry:publish-deploy-records": "DEBUG=snowball:* ts-node ./test/publish-deploy-records.ts",
|
||||
"test:registry:publish-deployment-removal-records": "DEBUG=snowball:* ts-node ./test/publish-deployment-removal-records.ts",
|
||||
"test:db:load:fixtures": "DEBUG=snowball:* ts-node ./test/initialize-db.ts",
|
||||
"test:db:delete": "DEBUG=snowball:* ts-node ./test/delete-db.ts"
|
||||
},
|
||||
|
||||
@@ -436,6 +436,19 @@ export class Database {
|
||||
return Boolean(updateResult.affected);
|
||||
}
|
||||
|
||||
async deleteDeploymentById (deploymentId: string): Promise<boolean> {
|
||||
const deploymentRepository = this.dataSource.getRepository(Deployment);
|
||||
const deployment = await deploymentRepository.findOneOrFail({
|
||||
where: {
|
||||
id: deploymentId
|
||||
}
|
||||
});
|
||||
|
||||
const deleteResult = await deploymentRepository.softRemove(deployment);
|
||||
|
||||
return Boolean(deleteResult);
|
||||
}
|
||||
|
||||
async addProject (user: User, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
|
||||
const projectRepository = this.dataSource.getRepository(Project);
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
OneToOne,
|
||||
JoinColumn
|
||||
JoinColumn,
|
||||
DeleteDateColumn
|
||||
} from 'typeorm';
|
||||
|
||||
import { Project } from './Project';
|
||||
import { Domain } from './Domain';
|
||||
import { User } from './User';
|
||||
import { AppDeploymentRecordAttributes } from '../types';
|
||||
import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types';
|
||||
|
||||
export enum Environment {
|
||||
Production = 'Production',
|
||||
@@ -24,6 +25,7 @@ export enum DeploymentStatus {
|
||||
Building = 'Building',
|
||||
Ready = 'Ready',
|
||||
Error = 'Error',
|
||||
Deleting = 'Deleting',
|
||||
}
|
||||
|
||||
export interface ApplicationDeploymentRequest {
|
||||
@@ -35,6 +37,18 @@ export interface ApplicationDeploymentRequest {
|
||||
meta: string;
|
||||
}
|
||||
|
||||
export interface ApplicationDeploymentRemovalRequest {
|
||||
type: string;
|
||||
version: string;
|
||||
deployment: string;
|
||||
}
|
||||
|
||||
export interface ApplicationDeploymentRemovalRequest {
|
||||
type: string;
|
||||
version: string;
|
||||
deployment: string;
|
||||
}
|
||||
|
||||
export interface ApplicationRecord {
|
||||
type: string;
|
||||
version: string;
|
||||
@@ -98,6 +112,18 @@ export class Deployment {
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRecordData!: AppDeploymentRecordAttributes | null;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
applicationDeploymentRemovalRequestId!: string | null;
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRemovalRequestData!: ApplicationDeploymentRemovalRequest | null;
|
||||
|
||||
@Column('varchar', { nullable: true })
|
||||
applicationDeploymentRemovalRecordId!: string | null;
|
||||
|
||||
@Column('simple-json', { nullable: true })
|
||||
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
|
||||
|
||||
@Column({
|
||||
enum: Environment
|
||||
@@ -121,4 +147,7 @@ export class Deployment {
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt!: Date | null;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,19 @@ import { RegistryConfig } from './config';
|
||||
import {
|
||||
ApplicationRecord,
|
||||
Deployment,
|
||||
ApplicationDeploymentRequest
|
||||
ApplicationDeploymentRequest,
|
||||
ApplicationDeploymentRemovalRequest
|
||||
} from './entity/Deployment';
|
||||
import { AppDeploymentRecord, PackageJSON } from './types';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, PackageJSON } from './types';
|
||||
import { sleep } from './utils';
|
||||
|
||||
const log = debug('snowball:registry');
|
||||
|
||||
const APP_RECORD_TYPE = 'ApplicationRecord';
|
||||
const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest';
|
||||
const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest';
|
||||
const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord';
|
||||
const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord';
|
||||
const SLEEP_DURATION = 1000;
|
||||
|
||||
// TODO: Move registry code to laconic-sdk/watcher-ts
|
||||
@@ -229,6 +232,74 @@ export class Registry {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRecords by filter
|
||||
*/
|
||||
async getDeploymentRecordsByFilter (filter: { [key: string]: any }): Promise<AppDeploymentRecord[]> {
|
||||
return this.registry.queryRecords(
|
||||
{
|
||||
type: APP_DEPLOYMENT_RECORD_TYPE,
|
||||
...filter
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch ApplicationDeploymentRemovalRecords for deployments
|
||||
*/
|
||||
async getDeploymentRemovalRecords (
|
||||
deployments: Deployment[]
|
||||
): Promise<AppDeploymentRemovalRecord[]> {
|
||||
// Fetch ApplicationDeploymentRemovalRecords for corresponding ApplicationDeploymentRecord set in deployments
|
||||
const records = await this.registry.queryRecords(
|
||||
{
|
||||
type: APP_DEPLOYMENT_REMOVAL_RECORD_TYPE
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// Filter records with ApplicationDeploymentRecord and ApplicationDeploymentRemovalRequest IDs
|
||||
return records.filter((record: AppDeploymentRemovalRecord) =>
|
||||
deployments.some(
|
||||
(deployment) =>
|
||||
deployment.applicationDeploymentRemovalRequestId === record.attributes.request &&
|
||||
deployment.applicationDeploymentRecordId === record.attributes.deployment
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async createApplicationDeploymentRemovalRequest (data: {
|
||||
deploymentId: string;
|
||||
}): Promise<{
|
||||
applicationDeploymentRemovalRequestId: string;
|
||||
applicationDeploymentRemovalRequestData: ApplicationDeploymentRemovalRequest;
|
||||
}> {
|
||||
const applicationDeploymentRemovalRequest = {
|
||||
type: APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE,
|
||||
version: '1.0.0',
|
||||
deployment: data.deploymentId
|
||||
};
|
||||
|
||||
const result = await this.registry.setRecord(
|
||||
{
|
||||
privateKey: this.registryConfig.privateKey,
|
||||
record: applicationDeploymentRemovalRequest,
|
||||
bondId: this.registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
this.registryConfig.fee
|
||||
);
|
||||
|
||||
log(`Application deployment removal request record published: ${result.data.id}`);
|
||||
log('Application deployment removal request data:', applicationDeploymentRemovalRequest);
|
||||
|
||||
return {
|
||||
applicationDeploymentRemovalRequestId: result.data.id,
|
||||
applicationDeploymentRemovalRequestData: applicationDeploymentRemovalRequest
|
||||
};
|
||||
}
|
||||
|
||||
getCrn (appName: string): string {
|
||||
assert(this.registryConfig.authority, "Authority doesn't exist");
|
||||
return `crn://${this.registryConfig.authority}/applications/${appName}`;
|
||||
|
||||
@@ -255,6 +255,20 @@ export const createResolvers = async (service: Service): Promise<any> => {
|
||||
}
|
||||
},
|
||||
|
||||
deleteDeployment: async (
|
||||
_: any,
|
||||
{
|
||||
deploymentId
|
||||
}: { deploymentId: string; }
|
||||
) => {
|
||||
try {
|
||||
return await service.deleteDeployment(deploymentId);
|
||||
} catch (err) {
|
||||
log(err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
addDomain: async (
|
||||
_: any,
|
||||
{ projectId, data }: { projectId: string; data: { name: string } }
|
||||
|
||||
@@ -19,6 +19,7 @@ enum DeploymentStatus {
|
||||
Building
|
||||
Ready
|
||||
Error
|
||||
Deleting
|
||||
}
|
||||
|
||||
enum DomainStatus {
|
||||
@@ -209,6 +210,7 @@ type Mutation {
|
||||
deleteProject(projectId: String!): Boolean!
|
||||
deleteDomain(domainId: String!): Boolean!
|
||||
rollbackDeployment(projectId: String!, deploymentId: String!): Boolean!
|
||||
deleteDeployment(deploymentId: String!): Boolean!
|
||||
addDomain(projectId: String!, data: AddDomainInput!): Boolean!
|
||||
updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean!
|
||||
authenticateGitHub(code: String!): AuthResult!
|
||||
|
||||
+156
-12
@@ -15,13 +15,16 @@ import { Permission, ProjectMember } from './entity/ProjectMember';
|
||||
import { User } from './entity/User';
|
||||
import { Registry } from './registry';
|
||||
import { GitHubConfig, RegistryConfig } from './config';
|
||||
import { AppDeploymentRecord, GitPushEventPayload, PackageJSON } from './types';
|
||||
import { AppDeploymentRecord, AppDeploymentRemovalRecord, GitPushEventPayload, PackageJSON } from './types';
|
||||
import { Role } from './entity/UserOrganization';
|
||||
|
||||
const log = debug('snowball:service');
|
||||
|
||||
const GITHUB_UNIQUE_WEBHOOK_ERROR = 'Hook already exists on this repository';
|
||||
|
||||
// Define a constant for an hour in milliseconds
|
||||
const HOUR = 1000 * 60 * 60;
|
||||
|
||||
interface Config {
|
||||
gitHubConfig: GitHubConfig;
|
||||
registryConfig: RegistryConfig;
|
||||
@@ -49,6 +52,8 @@ export class Service {
|
||||
init (): void {
|
||||
// Start check for ApplicationDeploymentRecords asynchronously
|
||||
this.checkDeployRecordsAndUpdate();
|
||||
// Start check for ApplicationDeploymentRemovalRecords asynchronously
|
||||
this.checkDeploymentRemovalRecordsAndUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,14 +65,13 @@ export class Service {
|
||||
|
||||
/**
|
||||
* Checks for ApplicationDeploymentRecord and update corresponding deployments
|
||||
* Continues check in loop after a delay of DEPLOY_RECORD_CHECK_DELAY_MS
|
||||
* Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
|
||||
*/
|
||||
async checkDeployRecordsAndUpdate (): Promise<void> {
|
||||
// Fetch deployments in building state
|
||||
const deployments = await this.db.getDeployments({
|
||||
where: {
|
||||
status: DeploymentStatus.Building
|
||||
// TODO: Fetch and check records for recent deployments
|
||||
}
|
||||
});
|
||||
|
||||
@@ -76,6 +80,28 @@ export class Service {
|
||||
`Found ${deployments.length} deployments in ${DeploymentStatus.Building} state`
|
||||
);
|
||||
|
||||
// Calculate a timestamp for one hour ago
|
||||
const anHourAgo = Date.now() - HOUR;
|
||||
|
||||
// Filter out deployments started more than an hour ago and mark them as Error
|
||||
const oldDeploymentsToUpdate = deployments.filter(
|
||||
deployment => (Number(deployment.updatedAt) < anHourAgo)
|
||||
)
|
||||
.map((deployment) => {
|
||||
return this.db.updateDeploymentById(deployment.id, {
|
||||
status: DeploymentStatus.Error,
|
||||
isCurrent: false
|
||||
});
|
||||
});
|
||||
|
||||
// If there are old deployments to update, log and perform the updates
|
||||
if (oldDeploymentsToUpdate.length > 0) {
|
||||
log(
|
||||
`Cleaning up ${oldDeploymentsToUpdate.length} deployments stuck in ${DeploymentStatus.Building} state for over an hour`
|
||||
);
|
||||
await Promise.all(oldDeploymentsToUpdate);
|
||||
}
|
||||
|
||||
// Fetch ApplicationDeploymentRecord for deployments
|
||||
const records = await this.registry.getDeploymentRecords(deployments);
|
||||
log(`Found ${records.length} ApplicationDeploymentRecords`);
|
||||
@@ -91,6 +117,38 @@ export class Service {
|
||||
}, this.config.registryConfig.fetchDeploymentRecordDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for ApplicationDeploymentRemovalRecord and remove corresponding deployments
|
||||
* Continues check in loop after a delay of registryConfig.fetchDeploymentRecordDelay
|
||||
*/
|
||||
async checkDeploymentRemovalRecordsAndUpdate (): Promise<void> {
|
||||
// Fetch deployments in deleting state
|
||||
const deployments = await this.db.getDeployments({
|
||||
where: {
|
||||
status: DeploymentStatus.Deleting
|
||||
}
|
||||
});
|
||||
|
||||
if (deployments.length) {
|
||||
log(
|
||||
`Found ${deployments.length} deployments in ${DeploymentStatus.Deleting} state`
|
||||
);
|
||||
|
||||
// Fetch ApplicationDeploymentRemovalRecords for deployments
|
||||
const records = await this.registry.getDeploymentRemovalRecords(deployments);
|
||||
log(`Found ${records.length} ApplicationDeploymentRemovalRecords`);
|
||||
|
||||
// Update deployments for which ApplicationDeploymentRemovalRecords were returned
|
||||
if (records.length) {
|
||||
await this.deleteDeploymentsWithRecordData(records, deployments);
|
||||
}
|
||||
}
|
||||
|
||||
this.deployRecordCheckTimeout = setTimeout(() => {
|
||||
this.checkDeploymentRemovalRecordsAndUpdate();
|
||||
}, this.config.registryConfig.fetchDeploymentRecordDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update deployments with ApplicationDeploymentRecord data
|
||||
*/
|
||||
@@ -153,6 +211,45 @@ export class Service {
|
||||
await Promise.all(deploymentUpdatePromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete deployments with ApplicationDeploymentRemovalRecord data
|
||||
*/
|
||||
async deleteDeploymentsWithRecordData (
|
||||
records: AppDeploymentRemovalRecord[],
|
||||
deployments: Deployment[],
|
||||
): Promise<void> {
|
||||
const removedApplicationDeploymentRecordIds = records.map(record => record.attributes.deployment);
|
||||
|
||||
// Get removed deployments for ApplicationDeploymentRecords
|
||||
const removedDeployments = deployments.filter(deployment => removedApplicationDeploymentRecordIds.includes(deployment.applicationDeploymentRecordId!))
|
||||
|
||||
const recordToDeploymentsMap = removedDeployments.reduce(
|
||||
(acc: { [key: string]: Deployment }, deployment) => {
|
||||
acc[deployment.applicationDeploymentRecordId!] = deployment;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// Update deployment data for ApplicationDeploymentRecords and delete
|
||||
const deploymentUpdatePromises = records.map(async (record) => {
|
||||
const deployment = recordToDeploymentsMap[record.attributes.deployment];
|
||||
|
||||
await this.db.updateDeploymentById(deployment.id, {
|
||||
applicationDeploymentRemovalRecordId: record.id,
|
||||
applicationDeploymentRemovalRecordData: record.attributes,
|
||||
});
|
||||
|
||||
log(
|
||||
`Updated deployment ${deployment.id} with ApplicationDeploymentRemovalRecord ${record.id}`
|
||||
);
|
||||
|
||||
await this.db.deleteDeploymentById(deployment.id)
|
||||
});
|
||||
|
||||
await Promise.all(deploymentUpdatePromises);
|
||||
}
|
||||
|
||||
async getUser (userId: string): Promise<User | null> {
|
||||
return this.db.getUser({
|
||||
where: {
|
||||
@@ -479,17 +576,10 @@ export class Service {
|
||||
return acc;
|
||||
}, {} as { [key: string]: string });
|
||||
|
||||
const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest(
|
||||
{
|
||||
deployment: newDeployment,
|
||||
appName: repo,
|
||||
repository: repoUrl,
|
||||
environmentVariables: environmentVariablesObj,
|
||||
dns: `${newDeployment.project.name}-${newDeployment.id}`
|
||||
});
|
||||
|
||||
// To set project DNS
|
||||
if (data.environment === Environment.Production) {
|
||||
// On deleting deployment later, project DNS deployment is also deleted
|
||||
// So publish project DNS deployment first so that ApplicationDeploymentRecord for the same is available when deleting deployment later
|
||||
await this.registry.createApplicationDeploymentRequest(
|
||||
{
|
||||
deployment: newDeployment,
|
||||
@@ -500,6 +590,15 @@ export class Service {
|
||||
});
|
||||
}
|
||||
|
||||
const { applicationDeploymentRequestId, applicationDeploymentRequestData } = await this.registry.createApplicationDeploymentRequest(
|
||||
{
|
||||
deployment: newDeployment,
|
||||
appName: repo,
|
||||
repository: repoUrl,
|
||||
environmentVariables: environmentVariablesObj,
|
||||
dns: `${newDeployment.project.name}-${newDeployment.id}`
|
||||
});
|
||||
|
||||
await this.db.updateDeploymentById(newDeployment.id, { applicationDeploymentRequestId, applicationDeploymentRequestData });
|
||||
|
||||
return newDeployment;
|
||||
@@ -717,6 +816,51 @@ export class Service {
|
||||
return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate;
|
||||
}
|
||||
|
||||
async deleteDeployment (deploymentId: string): Promise<boolean> {
|
||||
const deployment = await this.db.getDeployment({
|
||||
where: {
|
||||
id: deploymentId
|
||||
},
|
||||
relations: {
|
||||
project: true
|
||||
}
|
||||
});
|
||||
|
||||
if (deployment && deployment.applicationDeploymentRecordId) {
|
||||
// If deployment is current, remove deployment for project subdomain as well
|
||||
if (deployment.isCurrent) {
|
||||
const currentDeploymentURL = `https://${deployment.project.subDomain}`;
|
||||
|
||||
const deploymentRecords = await this.registry.getDeploymentRecordsByFilter({
|
||||
application: deployment.applicationRecordId,
|
||||
url: currentDeploymentURL
|
||||
})
|
||||
|
||||
if (!deploymentRecords.length) {
|
||||
log(`No ApplicationDeploymentRecord found for URL ${currentDeploymentURL} and ApplicationDeploymentRecord id ${deployment.applicationDeploymentRecordId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deploymentRecords[0].id });
|
||||
}
|
||||
|
||||
const result = await this.registry.createApplicationDeploymentRemovalRequest({ deploymentId: deployment.applicationDeploymentRecordId });
|
||||
|
||||
await this.db.updateDeploymentById(
|
||||
deployment.id,
|
||||
{
|
||||
status: DeploymentStatus.Deleting,
|
||||
applicationDeploymentRemovalRequestId: result.applicationDeploymentRemovalRequestId,
|
||||
applicationDeploymentRemovalRequestData: result.applicationDeploymentRemovalRequestData
|
||||
}
|
||||
);
|
||||
|
||||
return (result !== undefined || result !== null);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async addDomain (
|
||||
projectId: string,
|
||||
data: { name: string }
|
||||
|
||||
@@ -38,6 +38,13 @@ export interface AppDeploymentRecordAttributes {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface AppDeploymentRemovalRecordAttributes {
|
||||
deployment: string;
|
||||
request: string;
|
||||
type: "ApplicationDeploymentRemovalRecord";
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface RegistryRecord {
|
||||
id: string;
|
||||
names: string[] | null;
|
||||
@@ -50,3 +57,7 @@ interface RegistryRecord {
|
||||
export interface AppDeploymentRecord extends RegistryRecord {
|
||||
attributes: AppDeploymentRecordAttributes;
|
||||
}
|
||||
|
||||
export interface AppDeploymentRemovalRecord extends RegistryRecord {
|
||||
attributes: AppDeploymentRemovalRecordAttributes;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Registry } from '@snowballtools/laconic-sdk';
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
import { getConfig } from '../src/utils';
|
||||
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
|
||||
import { Deployment, DeploymentStatus, Environment } from '../src/entity/Deployment';
|
||||
|
||||
const log = debug('snowball:publish-deploy-records');
|
||||
|
||||
@@ -40,7 +40,7 @@ async function main () {
|
||||
});
|
||||
|
||||
for await (const deployment of deployments) {
|
||||
const url = `${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
|
||||
const url = `https://${deployment.project.name}-${deployment.id}.${misc.projectDomain}`;
|
||||
|
||||
const applicationDeploymentRecord = {
|
||||
type: 'ApplicationDeploymentRecord',
|
||||
@@ -71,6 +71,21 @@ async function main () {
|
||||
registryConfig.fee
|
||||
);
|
||||
|
||||
// Remove deployment for project subdomain if deployment is for production environment
|
||||
if (deployment.environment === Environment.Production) {
|
||||
applicationDeploymentRecord.url = `https://${deployment.project.subDomain}`
|
||||
|
||||
await registry.setRecord(
|
||||
{
|
||||
privateKey: registryConfig.privateKey,
|
||||
record: applicationDeploymentRecord,
|
||||
bondId: registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
registryConfig.fee
|
||||
);
|
||||
}
|
||||
|
||||
log('Application deployment record data:', applicationDeploymentRecord);
|
||||
log(`Application deployment record published: ${result.data.id}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import debug from 'debug';
|
||||
import { DataSource } from 'typeorm';
|
||||
import path from 'path';
|
||||
|
||||
import { Registry } from '@cerc-io/laconic-sdk';
|
||||
|
||||
import { Config } from '../src/config';
|
||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
||||
import { getConfig } from '../src/utils';
|
||||
import { Deployment, DeploymentStatus } from '../src/entity/Deployment';
|
||||
|
||||
const log = debug('snowball:publish-deployment-removal-records');
|
||||
|
||||
async function main () {
|
||||
const { registryConfig, database, misc } = await getConfig<Config>(DEFAULT_CONFIG_FILE_PATH);
|
||||
|
||||
const registry = new Registry(
|
||||
registryConfig.gqlEndpoint,
|
||||
registryConfig.restEndpoint,
|
||||
registryConfig.chainId
|
||||
);
|
||||
|
||||
const dataSource = new DataSource({
|
||||
type: 'better-sqlite3',
|
||||
database: database.dbPath,
|
||||
synchronize: true,
|
||||
entities: [path.join(__dirname, '../src/entity/*')]
|
||||
});
|
||||
|
||||
await dataSource.initialize();
|
||||
|
||||
const deploymentRepository = dataSource.getRepository(Deployment);
|
||||
const deployments = await deploymentRepository.find({
|
||||
relations: {
|
||||
project: true
|
||||
},
|
||||
where: {
|
||||
status: DeploymentStatus.Deleting
|
||||
}
|
||||
});
|
||||
|
||||
for await (const deployment of deployments) {
|
||||
const applicationDeploymentRemovalRecord = {
|
||||
type: "ApplicationDeploymentRemovalRecord",
|
||||
version: "1.0.0",
|
||||
deployment: deployment.applicationDeploymentRecordId,
|
||||
request: deployment.applicationDeploymentRemovalRequestId,
|
||||
}
|
||||
|
||||
const result = await registry.setRecord(
|
||||
{
|
||||
privateKey: registryConfig.privateKey,
|
||||
record: applicationDeploymentRemovalRecord,
|
||||
bondId: registryConfig.bondId
|
||||
},
|
||||
'',
|
||||
registryConfig.fee
|
||||
);
|
||||
|
||||
log('Application deployment removal record data:', applicationDeploymentRemovalRecord);
|
||||
log(`Application deployment removal record published: ${result.data.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
log(err);
|
||||
});
|
||||
@@ -9,9 +9,18 @@
|
||||
```
|
||||
|
||||
- Run script to deploy app
|
||||
```
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
|
||||
- To deploy frontend app to `dashboard.staging.apps.snowballtools.com`
|
||||
|
||||
```
|
||||
./deploy-frontend.staging.sh
|
||||
```
|
||||
|
||||
- To deploy frontend app to `dashboard.apps.snowballtools.com`
|
||||
|
||||
```
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
|
||||
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
cns:
|
||||
restEndpoint: 'http://console.laconic.com:1317'
|
||||
gqlEndpoint: 'http://console.laconic.com:9473/api'
|
||||
userKey: 87d00f66a73e2ca428adeb49ba9164d0ad9a87edc60e33d46ad3031b9c5701fe
|
||||
bondId: 098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661
|
||||
chainId: laconic_9000-1
|
||||
gas: 550000
|
||||
fees: 200000aphoton
|
||||
@@ -35,12 +35,12 @@ record:
|
||||
dns: dashboard
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
|
||||
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
LACONIC_HOSTED_CONFIG_github_clientid: b7c63b235ca1dd5639ab
|
||||
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
meta:
|
||||
note: Added by Snowball @ $CURRENT_DATE_TIME
|
||||
repository: "$REPO_URL"
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Repository URL
|
||||
REPO_URL="https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
|
||||
# Get the latest commit hash from the repository
|
||||
LATEST_HASH=$(git ls-remote $REPO_URL HEAD | awk '{print $1}')
|
||||
|
||||
# Extract version from ../frontend/package.json
|
||||
PACKAGE_VERSION=$(jq -r '.version' ../frontend/package.json)
|
||||
|
||||
# Current date and time for note
|
||||
CURRENT_DATE_TIME=$(date -u)
|
||||
|
||||
CONFIG_FILE=config.staging.yml
|
||||
REGISTRY_BOND_ID="098c906850b87412f02200e41f449bc79e055eab77acfef32c0b22443bb46661"
|
||||
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
# Get latest version from registry and increment application-record version
|
||||
NEW_APPLICATION_VERSION=$(yarn --silent laconic -c $CONFIG_FILE cns record list --type ApplicationRecord --all --name "staging-snowballtools-base-frontend" 2>/dev/null | jq -r -s ".[] | sort_by(.createTime) | reverse | [ .[] | select(.bondId == \"$REGISTRY_BOND_ID\") ] | .[0].attributes.version" | awk -F. -v OFS=. '{$NF += 1 ; print}')
|
||||
|
||||
if [ -z "$NEW_APPLICATION_VERSION" ] || [ "1" == "$NEW_APPLICATION_VERSION" ]; then
|
||||
# Set application-record version if no previous records were found
|
||||
NEW_APPLICATION_VERSION=0.0.1
|
||||
fi
|
||||
|
||||
# Generate application-deployment-request.yml
|
||||
cat > ./staging-records/application-deployment-request.yml <<EOF
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
name: staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@$PACKAGE_VERSION
|
||||
dns: dashboard.staging.apps.snowballtools.com
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
|
||||
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
|
||||
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
|
||||
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
|
||||
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
|
||||
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: localhost
|
||||
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
|
||||
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
||||
meta:
|
||||
note: Added by Snowball @ $CURRENT_DATE_TIME
|
||||
repository: "$REPO_URL"
|
||||
repository_ref: $LATEST_HASH
|
||||
EOF
|
||||
|
||||
# Generate application-record.yml with incremented version
|
||||
cat > ./staging-records/application-record.yml <<EOF
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: $NEW_APPLICATION_VERSION
|
||||
repository_ref: $LATEST_HASH
|
||||
repository: ["$REPO_URL"]
|
||||
app_type: webapp
|
||||
name: staging-snowballtools-base-frontend
|
||||
app_version: $PACKAGE_VERSION
|
||||
EOF
|
||||
|
||||
echo "Files generated successfully."
|
||||
|
||||
RECORD_FILE=staging-records/application-record.yml
|
||||
|
||||
# Publish ApplicationRecord
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_CRN="crn://staging-snowballtools/applications/staging-snowballtools-base-frontend"
|
||||
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${PACKAGE_VERSION}" "$RECORD_ID"
|
||||
sleep 2
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${LATEST_HASH}" "$RECORD_ID"
|
||||
sleep 2
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
|
||||
|
||||
# Check if record found for REGISTRY_APP_CRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RECORD_FILE=staging-records/application-deployment-request.yml
|
||||
|
||||
sleep 2
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
@@ -0,0 +1,24 @@
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
name: staging-snowballtools-base-frontend@0.0.0
|
||||
application: crn://staging-snowballtools/applications/staging-snowballtools-base-frontend@0.0.0
|
||||
dns: dashboard.staging.apps.snowballtools.com
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_server_url: https://snowballtools-base-api.staging.apps.snowballtools.com
|
||||
LACONIC_HOSTED_CONFIG_github_clientid: 905c09553f527d2cdff5
|
||||
LACONIC_HOSTED_CONFIG_github_templaterepo: snowball-tools/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_github_pwa_templaterepo: snowball-tools/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
LACONIC_HOSTED_CONFIG_lit_relay_api_key: 15DDD969-E75F-404D-AAD9-58A37C4FD354_snowball
|
||||
LACONIC_HOSTED_CONFIG_aplchemy_api_key: THvPart_gqI5x02RNYSBntlmwA66I_qc
|
||||
LACONIC_HOSTED_CONFIG_bugsnag_api_key: 8c480cd5386079f9dd44f9581264a073
|
||||
LACONIC_HOSTED_CONFIG_passkey_wallet_rpid: localhost
|
||||
LACONIC_HOSTED_CONFIG_turnkey_api_base_url: https://api.turnkey.com/
|
||||
LACONIC_HOSTED_CONFIG_turnkey_organization_id: 5049ae99-5bca-40b3-8317-504384d4e591
|
||||
meta:
|
||||
note: Added by Snowball @ Tuesday 21 May 2024 06:17:23 AM UTC
|
||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
@@ -0,0 +1,8 @@
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.2
|
||||
repository_ref: 8488cfab8353321ed05c4234bf1b914c9ad3aa99
|
||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||
app_type: webapp
|
||||
name: staging-snowballtools-base-frontend
|
||||
app_version: 0.0.0
|
||||
@@ -102,4 +102,4 @@
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ export const ProjectCard = ({
|
||||
navigate(`projects/${project.id}`);
|
||||
}, [project.id, navigate]);
|
||||
|
||||
const navigateToSettingsOnClick = useCallback(
|
||||
(
|
||||
e: React.MouseEvent<HTMLLIElement> | React.MouseEvent<HTMLButtonElement>,
|
||||
) => {
|
||||
e.stopPropagation();
|
||||
navigate(`projects/${project.id}/settings`);
|
||||
},
|
||||
[project.id, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@@ -92,8 +102,15 @@ export const ProjectCard = ({
|
||||
</Button>
|
||||
</MenuHandler>
|
||||
<MenuList>
|
||||
<MenuItem>Project settings</MenuItem>
|
||||
<MenuItem className="text-red-500">Delete project</MenuItem>
|
||||
<MenuItem onClick={navigateToSettingsOnClick}>
|
||||
Project settings
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="text-red-500"
|
||||
onClick={navigateToSettingsOnClick}
|
||||
>
|
||||
Delete project
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
@@ -155,13 +155,13 @@ export const RepositoryList = () => {
|
||||
{Boolean(repositoryDetails.length) ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
{repositoryDetails.map((repo, index) => (
|
||||
<>
|
||||
<ProjectRepoCard repository={repo} key={index} />
|
||||
<div key={index}>
|
||||
<ProjectRepoCard repository={repo} />
|
||||
{/* Horizontal line */}
|
||||
{index !== repositoryDetails.length - 1 && (
|
||||
<div className="border-b border-border-separator/[0.06] w-full" />
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
+5
-1
@@ -38,6 +38,7 @@ const STATUS_COLORS: {
|
||||
[DeploymentStatus.Building]: 'emphasized',
|
||||
[DeploymentStatus.Ready]: 'positive',
|
||||
[DeploymentStatus.Error]: 'negative',
|
||||
[DeploymentStatus.Deleting]: 'neutral',
|
||||
};
|
||||
|
||||
const DeploymentDetailsCard = ({
|
||||
@@ -48,7 +49,10 @@ const DeploymentDetailsCard = ({
|
||||
prodBranchDomains,
|
||||
}: DeployDetailsCardProps) => {
|
||||
const getIconByDeploymentStatus = (status: DeploymentStatus) => {
|
||||
if (status === DeploymentStatus.Building) {
|
||||
if (
|
||||
status === DeploymentStatus.Building ||
|
||||
status === DeploymentStatus.Deleting
|
||||
) {
|
||||
return <LoadingIcon className="animate-spin" />;
|
||||
}
|
||||
if (status === DeploymentStatus.Ready) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
RefreshIcon,
|
||||
RocketIcon,
|
||||
UndoIcon,
|
||||
CrossCircleIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import {
|
||||
Menu,
|
||||
@@ -79,6 +80,16 @@ export const DeploymentMenu = ({
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDeployment = async () => {
|
||||
const isDeleted = await client.deleteDeployment(deployment.id);
|
||||
if (isDeleted) {
|
||||
await onUpdate();
|
||||
toast.success('Deleted deployment');
|
||||
} else {
|
||||
toast.error('Unable to delete deployment');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn('max-w-[32px]', className)} {...props}>
|
||||
@@ -147,6 +158,12 @@ export const DeploymentMenu = ({
|
||||
>
|
||||
<UndoIcon /> Rollback to this version
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
className="hover:bg-base-bg-emphasized flex items-center gap-3"
|
||||
onClick={() => deleteDeployment()}
|
||||
>
|
||||
<CrossCircleIcon /> Delete deployment
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
+4
-11
@@ -19,7 +19,7 @@ const AddEnvironmentVariableRow = ({
|
||||
isDeleteDisabled,
|
||||
}: AddEnvironmentVariableRowProps) => {
|
||||
return (
|
||||
<div className="flex py-4 self-stretch">
|
||||
<div className="flex gap-2 py-1 self-stretch items-end">
|
||||
<Input
|
||||
size="md"
|
||||
{...register(`variables.${index}.key`, {
|
||||
@@ -34,16 +34,9 @@ const AddEnvironmentVariableRow = ({
|
||||
required: 'Value field cannot be empty',
|
||||
})}
|
||||
/>
|
||||
<div className="self-end">
|
||||
<Button
|
||||
size="md"
|
||||
iconOnly
|
||||
onClick={onDelete}
|
||||
disabled={isDeleteDisabled}
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Button size="md" iconOnly onClick={onDelete} disabled={isDeleteDisabled}>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+35
-57
@@ -1,20 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Project } from 'gql-client';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
Input,
|
||||
Typography,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
import { Modal } from 'components/shared/Modal';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { Input } from 'components/shared/Input';
|
||||
import { Project } from 'gql-client';
|
||||
|
||||
interface DeleteProjectDialogProp {
|
||||
open: boolean;
|
||||
@@ -60,51 +53,36 @@ const DeleteProjectDialog = ({
|
||||
}, [client, project, handleOpen]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} handler={handleOpen}>
|
||||
<DialogHeader className="flex justify-between">
|
||||
<div>Delete project?</div>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleOpen}
|
||||
className="mr-1 rounded-3xl"
|
||||
>
|
||||
X
|
||||
</Button>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubmit(deleteProjectHandler)}>
|
||||
<DialogBody className="flex flex-col gap-2">
|
||||
<Typography variant="paragraph">
|
||||
Deleting your project is irreversible. Enter your project’s
|
||||
name
|
||||
<span className="bg-blue-100 text-blue-700">({project.name})</span>
|
||||
below to confirm you want to permanently delete it:
|
||||
</Typography>
|
||||
<Input
|
||||
id="input"
|
||||
{...register('projectName', {
|
||||
required: 'Project name is required',
|
||||
validate: (value) => value === project.name,
|
||||
})}
|
||||
/>
|
||||
<Typography variant="small" color="red">
|
||||
^ Deleting your project is irreversible.
|
||||
</Typography>
|
||||
</DialogBody>
|
||||
<DialogFooter className="flex justify-start">
|
||||
<Button variant="outlined" onClick={handleOpen} className="mr-1">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="gradient"
|
||||
color="red"
|
||||
type="submit"
|
||||
disabled={!isValid}
|
||||
>
|
||||
Yes, Delete project
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Dialog>
|
||||
<Modal open={open} onOpenChange={handleOpen}>
|
||||
<Modal.Content>
|
||||
<Modal.Header>Delete project?</Modal.Header>
|
||||
<form onSubmit={handleSubmit(deleteProjectHandler)}>
|
||||
<Modal.Body>
|
||||
<Input
|
||||
label={
|
||||
"Deleting your project is irreversible. Enter your project's name " +
|
||||
project.name +
|
||||
' below to confirm you want to permanently delete it:'
|
||||
}
|
||||
id="input"
|
||||
{...register('projectName', {
|
||||
required: 'Project name is required',
|
||||
validate: (value) => value === project.name,
|
||||
})}
|
||||
helperText="Deleting your project is irreversible."
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Footer className="flex justify-start">
|
||||
<Button onClick={handleOpen} variant="tertiary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" type="submit" disabled={!isValid}>
|
||||
Yes, delete project
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</form>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+94
-80
@@ -1,16 +1,20 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import toast from 'react-hot-toast';
|
||||
import { EnvironmentVariable } from 'gql-client';
|
||||
|
||||
import {
|
||||
IconButton,
|
||||
Typography,
|
||||
} from '@snowballtools/material-tailwind-react-fork';
|
||||
|
||||
import { useGQLClient } from 'context/GQLClientContext';
|
||||
import { DeleteVariableDialog } from 'components/projects/Dialog/DeleteVariableDialog';
|
||||
import { Input } from 'components/shared/Input';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import {
|
||||
CheckRoundFilledIcon,
|
||||
CrossIcon,
|
||||
EditBigIcon,
|
||||
HideEyeOffIcon,
|
||||
ShowEyeIcon,
|
||||
TrashIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { EnvironmentVariable } from 'gql-client';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
|
||||
const ShowPasswordIcon = ({
|
||||
handler,
|
||||
@@ -26,19 +30,24 @@ const ShowPasswordIcon = ({
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{isVisible ? '-' : '+'}
|
||||
{isVisible ? <ShowEyeIcon size={16} /> : <HideEyeOffIcon size={16} />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export interface EditEnvironmentVariableRowProps {
|
||||
variable: EnvironmentVariable;
|
||||
onUpdate: () => Promise<void>;
|
||||
isFirstVariable?: boolean;
|
||||
}
|
||||
|
||||
const EditEnvironmentVariableRow = ({
|
||||
variable,
|
||||
onUpdate,
|
||||
}: {
|
||||
variable: EnvironmentVariable;
|
||||
onUpdate: () => Promise<void>;
|
||||
}) => {
|
||||
isFirstVariable = false,
|
||||
}: EditEnvironmentVariableRowProps) => {
|
||||
const client = useGQLClient();
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const { handleSubmit, register, reset } = useForm({
|
||||
defaultValues: {
|
||||
@@ -58,9 +67,19 @@ const EditEnvironmentVariableRow = ({
|
||||
if (isEnvironmentVariableRemoved) {
|
||||
await onUpdate();
|
||||
setDeleteDialogOpen((preVal) => !preVal);
|
||||
toast.success('Variable deleted');
|
||||
toast({
|
||||
id: 'variable_deleted',
|
||||
title: 'Variable deleted',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
} else {
|
||||
toast.error('Variable not deleted');
|
||||
toast({
|
||||
id: 'variable_not_deleted',
|
||||
title: 'Variable not deleted',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
}, [variable, onUpdate]);
|
||||
|
||||
@@ -71,10 +90,21 @@ const EditEnvironmentVariableRow = ({
|
||||
|
||||
if (isEnvironmentVariableUpdated) {
|
||||
await onUpdate();
|
||||
toast.success('Variable edited');
|
||||
toast({
|
||||
id: 'variable_updated',
|
||||
title: 'Variable edited',
|
||||
variant: 'success',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
|
||||
setEdit((preVal) => !preVal);
|
||||
} else {
|
||||
toast.error('Variable not edited');
|
||||
toast({
|
||||
id: 'variable_not_updated',
|
||||
title: 'Variable not edited',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
}
|
||||
},
|
||||
[variable, onUpdate],
|
||||
@@ -86,71 +116,55 @@ const EditEnvironmentVariableRow = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-1 p-2">
|
||||
<div>
|
||||
<Typography variant="small">Key</Typography>
|
||||
<Input disabled={!edit} {...register(`key`)} />
|
||||
</div>
|
||||
<div>
|
||||
<Typography variant="small">Value</Typography>
|
||||
<Input
|
||||
disabled={!edit}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
leftIcon={
|
||||
<ShowPasswordIcon
|
||||
handler={() => {
|
||||
setShowPassword((prevShowPassword) => !prevShowPassword);
|
||||
}}
|
||||
isVisible={showPassword}
|
||||
/>
|
||||
<div className="flex gap-1 items-end">
|
||||
<Input
|
||||
disabled={!edit}
|
||||
{...register(`key`)}
|
||||
label={isFirstVariable ? 'Key' : undefined}
|
||||
/>
|
||||
<Input
|
||||
disabled={!edit}
|
||||
type={showPassword || edit ? 'text' : 'password'}
|
||||
label={isFirstVariable ? 'Value' : undefined}
|
||||
rightIcon={
|
||||
<ShowPasswordIcon
|
||||
handler={() => {
|
||||
setShowPassword((prevShowPassword) => !prevShowPassword);
|
||||
}}
|
||||
isVisible={showPassword || edit}
|
||||
/>
|
||||
}
|
||||
{...register(`value`)}
|
||||
/>
|
||||
<Button
|
||||
iconOnly
|
||||
size="md"
|
||||
onClick={() => {
|
||||
edit
|
||||
? handleSubmit(updateEnvironmentVariableHandler)()
|
||||
: setEdit((preVal) => !preVal);
|
||||
}}
|
||||
>
|
||||
{edit ? (
|
||||
<CheckRoundFilledIcon size={16} />
|
||||
) : (
|
||||
<EditBigIcon size={16} />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
iconOnly
|
||||
size="md"
|
||||
onClick={() => {
|
||||
if (edit) {
|
||||
reset();
|
||||
setEdit((preVal) => !preVal);
|
||||
} else {
|
||||
setDeleteDialogOpen((preVal) => !preVal);
|
||||
}
|
||||
{...register(`value`)}
|
||||
/>
|
||||
</div>
|
||||
{edit ? (
|
||||
<>
|
||||
<div className="self-end">
|
||||
<IconButton
|
||||
onClick={handleSubmit(updateEnvironmentVariableHandler)}
|
||||
size="sm"
|
||||
>
|
||||
{'S'}
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="self-end">
|
||||
<IconButton
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
reset();
|
||||
setEdit((preVal) => !preVal);
|
||||
}}
|
||||
>
|
||||
{'C'}
|
||||
</IconButton>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="self-end">
|
||||
<IconButton
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setEdit((preVal) => !preVal);
|
||||
}}
|
||||
>
|
||||
{'E'}
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="self-end">
|
||||
<IconButton
|
||||
size="sm"
|
||||
onClick={() => setDeleteDialogOpen((preVal) => !preVal)}
|
||||
>
|
||||
{'D'}
|
||||
</IconButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
}}
|
||||
>
|
||||
{edit ? <CrossIcon size={16} /> : <TrashIcon size={16} />}
|
||||
</Button>
|
||||
</div>
|
||||
<DeleteVariableDialog
|
||||
handleCancel={() => setDeleteDialogOpen((preVal) => !preVal)}
|
||||
|
||||
@@ -80,7 +80,7 @@ const MemberCard = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex p-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
|
||||
className={`flex py-1 items-center ${!isFirstCard && 'mt-1 border-t border-gray-300'}`}
|
||||
>
|
||||
<div className="basis-1/2">
|
||||
{member.name && (
|
||||
|
||||
@@ -30,7 +30,7 @@ abbr[title] {
|
||||
@apply !text-elements-disabled !bg-transparent;
|
||||
}
|
||||
|
||||
.react-calendar__month-view__days__day--neighboringMonth {
|
||||
.react-calendar__month-view__days__day--neighboringMonth:focus-visible {
|
||||
@apply !text-elements-disabled !bg-transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ export const CollaboratorsIcon = (props: CustomIconProps) => {
|
||||
<path
|
||||
d="M12.5002 2.08301C14.3411 2.08301 15.8335 3.57539 15.8335 5.41634C15.8335 7.25729 14.3411 8.74968 12.5002 8.74968M14.5835 11.1659C17.2451 12.0374 19.1668 14.498 19.1668 17.083H16.6668M7.50016 8.74968C5.65921 8.74968 4.16683 7.25729 4.16683 5.41634C4.16683 3.57539 5.65921 2.08301 7.50016 2.08301C9.34111 2.08301 10.8335 3.57539 10.8335 5.41634C10.8335 7.25729 9.34111 8.74968 7.50016 8.74968ZM0.833496 17.083C0.833496 13.8613 3.81826 10.833 7.50016 10.833C11.1821 10.833 14.1668 13.8613 14.1668 17.083H0.833496Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="square"
|
||||
strokeLinecap="square"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
|
||||
@@ -12,8 +12,8 @@ export const CopyUnfilledIcon = (props: CustomIconProps) => {
|
||||
<path
|
||||
d="M6 5.625V5.025C6 4.18492 6 3.76488 6.16349 3.44401C6.3073 3.16177 6.53677 2.9323 6.81901 2.78849C7.13988 2.625 7.55992 2.625 8.4 2.625H12.975C13.8151 2.625 14.2351 2.625 14.556 2.78849C14.8382 2.9323 15.0677 3.16177 15.2115 3.44401C15.375 3.76488 15.375 4.18492 15.375 5.025V9.975C15.375 10.8151 15.375 11.2351 15.2115 11.556C15.0677 11.8382 14.8382 12.0677 14.556 12.2115C14.2351 12.375 13.8151 12.375 12.975 12.375H12.375M12.375 8.025V12.975C12.375 13.8151 12.375 14.2351 12.2115 14.556C12.0677 14.8382 11.8382 15.0677 11.556 15.2115C11.2351 15.375 10.8151 15.375 9.975 15.375H5.025C4.18492 15.375 3.76488 15.375 3.44401 15.2115C3.16177 15.0677 2.9323 14.8382 2.78849 14.556C2.625 14.2351 2.625 13.8151 2.625 12.975V8.025C2.625 7.18492 2.625 6.76488 2.78849 6.44401C2.9323 6.16177 3.16177 5.9323 3.44401 5.78849C3.76488 5.625 4.18492 5.625 5.025 5.625H9.975C10.8151 5.625 11.2351 5.625 11.556 5.78849C11.8382 5.9323 12.0677 6.16177 12.2115 6.44401C12.375 6.76488 12.375 7.18492 12.375 8.025Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const EditBigIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.61304 1.99935L7.5 1.99935C7.77614 1.99935 8 2.22321 8 2.49935C8 2.77549 7.77614 2.99935 7.5 2.99935H4.63333C4.25171 2.99935 3.99557 2.99974 3.79832 3.01585C3.6069 3.03149 3.51538 3.05941 3.45501 3.09018C3.29821 3.17007 3.17072 3.29755 3.09083 3.45436C3.06007 3.51473 3.03214 3.60625 3.01651 3.79767C3.00039 3.99492 3 4.25106 3 4.63268V11.366C3 11.7476 3.00039 12.0038 3.01651 12.201C3.03214 12.3924 3.06007 12.484 3.09083 12.5443C3.17072 12.7011 3.29821 12.8286 3.45501 12.9085C3.51538 12.9393 3.6069 12.9672 3.79832 12.9828C3.99557 12.999 4.25171 12.9993 4.63333 12.9993H11.3667C11.7483 12.9993 12.0044 12.999 12.2017 12.9828C12.3931 12.9672 12.4846 12.9393 12.545 12.9085C12.7018 12.8286 12.8293 12.7011 12.9092 12.5443C12.9399 12.484 12.9679 12.3924 12.9835 12.201C12.9996 12.0038 13 11.7476 13 11.366V8.49935C13 8.22321 13.2239 7.99935 13.5 7.99935C13.7761 7.99935 14 8.22321 14 8.49935V11.3863C14 11.7424 14 12.0396 13.9802 12.2825C13.9595 12.5357 13.9147 12.7735 13.8002 12.9983C13.6244 13.3433 13.3439 13.6238 12.999 13.7995C12.7741 13.9141 12.5364 13.9588 12.2831 13.9795C12.0403 13.9994 11.7431 13.9994 11.387 13.9993H4.61303C4.25693 13.9994 3.95971 13.9994 3.71689 13.9795C3.46363 13.9588 3.22586 13.9141 3.00102 13.7995C2.65605 13.6238 2.37559 13.3433 2.19982 12.9983C2.08526 12.7735 2.04052 12.5357 2.01983 12.2825C1.99999 12.0396 1.99999 11.7424 2 11.3863V4.61239C1.99999 4.25629 1.99999 3.95906 2.01983 3.71624C2.04052 3.46298 2.08526 3.22521 2.19982 3.00037C2.37559 2.6554 2.65605 2.37494 3.00102 2.19917C3.22586 2.08461 3.46363 2.03987 3.71689 2.01918C3.95971 1.99934 4.25694 1.99934 4.61304 1.99935Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M13.963 1.92174C13.2471 1.20578 12.0863 1.20578 11.3703 1.92174L5.67504 7.617C5.45625 7.83579 5.33333 8.13254 5.33333 8.44196V10.1658C5.33333 10.442 5.55719 10.6658 5.83333 10.6658H7.55719C7.86661 10.6658 8.16336 10.5429 8.38215 10.3241L14.0774 4.62884C14.7934 3.91288 14.7934 2.75208 14.0774 2.03612L13.963 1.92174Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const HideEyeOffIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M2.02012 1.31246C1.82486 1.1172 1.50828 1.1172 1.31301 1.31246C1.11775 1.50772 1.11775 1.82431 1.31301 2.01957L2.02012 1.31246ZM1.62744 7.42685L1.1887 7.18704L1.62744 7.42685ZM11.7196 11.719L12.0731 11.3655L11.7196 11.719ZM13.9797 14.6862C14.1749 14.8815 14.4915 14.8815 14.6868 14.6862C14.8821 14.491 14.8821 14.1744 14.6868 13.9791L13.9797 14.6862ZM1.62804 8.57302L1.18932 8.81287H1.18932L1.62804 8.57302ZM5.2498 3.12216C4.99958 3.23898 4.89145 3.53652 5.00828 3.78674C5.1251 4.03695 5.42264 4.14508 5.67286 4.02826L5.2498 3.12216ZM14.3718 7.42567L14.8105 7.18582L14.3718 7.42567ZM12.3668 10.4904C12.1743 10.6884 12.1786 11.0049 12.3766 11.1975C12.5745 11.39 12.891 11.3857 13.0836 11.1877L12.3668 10.4904ZM14.3724 8.57183L13.9336 8.33203V8.33203L14.3724 8.57183ZM6.58569 6.58512L6.93924 6.23157C6.74398 6.03631 6.4274 6.03631 6.23213 6.23157L6.58569 6.58512ZM9.41411 9.41355L9.76767 9.7671C9.86144 9.67334 9.91411 9.54616 9.91411 9.41355C9.91411 9.28094 9.86144 9.15377 9.76767 9.06L9.41411 9.41355ZM1.31301 2.01957L3.92667 4.63323L4.63378 3.92612L2.02012 1.31246L1.31301 2.01957ZM2.06618 7.66665C2.78183 6.35732 3.64551 5.3672 4.57648 4.68246L3.98397 3.87689C2.91986 4.65956 1.96456 5.76756 1.1887 7.18704L2.06618 7.66665ZM3.92667 4.63323L11.366 12.0726L12.0731 11.3655L4.63378 3.92612L3.92667 4.63323ZM11.366 12.0726L13.9797 14.6862L14.6868 13.9791L12.0731 11.3655L11.366 12.0726ZM1.18932 8.81287C2.47954 11.1728 4.26886 12.674 6.21987 13.243C8.17733 13.8138 10.2399 13.428 12.0158 12.1218L11.4233 11.3162C9.88108 12.4506 8.13835 12.7608 6.49983 12.283C4.85485 11.8033 3.25748 10.5112 2.06675 8.33317L1.18932 8.81287ZM1.1887 7.18704C0.911724 7.69378 0.912792 8.30707 1.18932 8.81287L2.06675 8.33317C1.95313 8.12535 1.95308 7.87358 2.06618 7.66665L1.1887 7.18704ZM5.67286 4.02826C7.09829 3.36272 8.63166 3.32591 10.0669 3.91426C11.5069 4.50457 12.8795 5.7385 13.933 7.66553L14.8105 7.18582C13.6688 5.09759 12.1371 3.68216 10.4462 2.98899C8.75047 2.29385 6.92622 2.33943 5.2498 3.12216L5.67286 4.02826ZM13.0836 11.1877C13.7227 10.5308 14.305 9.7376 14.8111 8.81164L13.9336 8.33203C13.4669 9.18586 12.9371 9.90419 12.3668 10.4904L13.0836 11.1877ZM13.933 7.66553C14.0467 7.87336 14.0467 8.12511 13.9336 8.33203L14.8111 8.81164C15.0881 8.30489 15.087 7.69161 14.8105 7.18582L13.933 7.66553ZM7.9999 9.49934C7.17147 9.49934 6.4999 8.82776 6.4999 7.99934H5.4999C5.4999 9.38005 6.61919 10.4993 7.9999 10.4993V9.49934ZM6.4999 7.99934C6.4999 7.58499 6.66725 7.21067 6.93924 6.93868L6.23213 6.23157C5.78027 6.68343 5.4999 7.30912 5.4999 7.99934H6.4999ZM6.23213 6.93868L9.06056 9.7671L9.76767 9.06L6.93924 6.23157L6.23213 6.93868ZM9.06056 9.06C8.78857 9.33199 8.41425 9.49934 7.9999 9.49934V10.4993C8.69012 10.4993 9.3158 10.219 9.76767 9.7671L9.06056 9.06Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const ShowEyeIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M1.62774 7.42694L2.06646 7.66677L1.62774 7.42694ZM14.3721 7.42686L13.9333 7.66669V7.66669L14.3721 7.42686ZM1.62774 8.5731L2.06646 8.33327H2.06646L1.62774 8.5731ZM14.3721 8.57302L14.8108 8.81285L14.8108 8.81285L14.3721 8.57302ZM2.06646 7.66677C3.61474 4.83447 5.84609 3.50002 7.99991 3.5C10.1537 3.49998 12.3851 4.8344 13.9333 7.66669L14.8108 7.18703C13.1325 4.11679 10.6049 2.49998 7.9999 2.5C5.39486 2.50002 2.86735 4.11687 1.18901 7.18711L2.06646 7.66677ZM1.18901 8.81293C2.86735 11.8832 5.39486 13.5 7.99991 13.5C10.6049 13.4999 13.1325 11.8831 14.8108 8.81285L13.9333 8.33319C12.3851 11.1655 10.1537 12.4999 7.9999 12.5C5.84609 12.5 3.61473 11.1656 2.06646 8.33327L1.18901 8.81293ZM1.18901 7.18711C0.912257 7.69338 0.912257 8.30665 1.18901 8.81293L2.06646 8.33327C1.95311 8.1259 1.95311 7.87414 2.06646 7.66677L1.18901 7.18711ZM13.9333 7.66669C14.0467 7.87406 14.0467 8.12582 13.9333 8.33319L14.8108 8.81285C15.0875 8.30658 15.0875 7.69331 14.8108 7.18703L13.9333 7.66669ZM9.4999 8C9.4999 8.82843 8.82833 9.5 7.9999 9.5V10.5C9.38062 10.5 10.4999 9.38071 10.4999 8H9.4999ZM7.9999 9.5C7.17148 9.5 6.4999 8.82843 6.4999 8H5.4999C5.4999 9.38071 6.61919 10.5 7.9999 10.5V9.5ZM6.4999 8C6.4999 7.17157 7.17148 6.5 7.9999 6.5V5.5C6.61919 5.5 5.4999 6.61929 5.4999 8H6.4999ZM7.9999 6.5C8.82833 6.5 9.4999 7.17157 9.4999 8H10.4999C10.4999 6.61929 9.38062 5.5 7.9999 5.5V6.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -10,8 +10,8 @@ export const TrashIcon: React.FC<CustomIconProps> = (props) => {
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.05612 3.33398C5.52062 2.16265 6.66341 1.33398 8.00079 1.33398C9.33816 1.33398 10.481 2.16265 10.9455 3.33398H14.1668C14.443 3.33398 14.6668 3.55784 14.6668 3.83398C14.6668 4.11013 14.443 4.33398 14.1668 4.33398H13.3023L12.7463 12.952C12.684 13.9167 11.8834 14.6673 10.9167 14.6673H5.08358C4.11688 14.6673 3.31629 13.9167 3.25405 12.952L2.69805 4.33398H1.8335C1.55735 4.33398 1.3335 4.11013 1.3335 3.83398C1.3335 3.55784 1.55735 3.33398 1.8335 3.33398H5.05612ZM6.17457 3.33398C6.55973 2.73248 7.23408 2.33398 8.00079 2.33398C8.76749 2.33398 9.44184 2.73248 9.827 3.33398H6.17457ZM7.00016 7.16732C7.00016 6.89118 6.77631 6.66732 6.50016 6.66732C6.22402 6.66732 6.00016 6.89118 6.00016 7.16732V10.834C6.00016 11.1101 6.22402 11.334 6.50016 11.334C6.77631 11.334 7.00016 11.1101 7.00016 10.834V7.16732ZM9.50016 6.66732C9.77631 6.66732 10.0002 6.89118 10.0002 7.16732V10.834C10.0002 11.1101 9.77631 11.334 9.50016 11.334C9.22402 11.334 9.00016 11.1101 9.00016 10.834V7.16732C9.00016 6.89118 9.22402 6.66732 9.50016 6.66732Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
|
||||
@@ -73,6 +73,9 @@ export * from './SwitchIcon';
|
||||
export * from './CollaboratorsIcon';
|
||||
export * from './ChevronUpSmallIcon';
|
||||
export * from './ChevronDownSmallIcon';
|
||||
export * from './EditBigIcon';
|
||||
export * from './ShowEyeIcon';
|
||||
export * from './HideEyeOffIcon';
|
||||
|
||||
// Templates
|
||||
export * from './templates';
|
||||
|
||||
@@ -107,7 +107,6 @@ export const UserSelect = ({ options, value }: UserSelectProps) => {
|
||||
ref: inputWrapperRef,
|
||||
suppressRefError: true,
|
||||
})}
|
||||
ref={inputWrapperRef}
|
||||
onClick={() => !dropdownOpen && openMenu()}
|
||||
className="cursor-pointer relative py-2 pl-2 pr-4 flex min-w-[200px] w-full items-center justify-between rounded-xl bg-surface-card shadow-sm"
|
||||
>
|
||||
|
||||
@@ -19,10 +19,7 @@ const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement,
|
||||
);
|
||||
|
||||
assert(
|
||||
import.meta.env.VITE_SERVER_URL,
|
||||
'REACT_APP_SERVER_URL is not set in env',
|
||||
);
|
||||
assert(import.meta.env.VITE_SERVER_URL, 'VITE_SERVER_URL is not set in env');
|
||||
const gqlEndpoint = `${import.meta.env.VITE_SERVER_URL}/${SERVER_GQL_PATH}`;
|
||||
|
||||
const gqlClient = new GQLClient({ gqlEndpoint });
|
||||
|
||||
@@ -99,8 +99,8 @@ const CreateRepo = () => {
|
||||
|
||||
console.error((err as Error).message);
|
||||
toast({
|
||||
id: 'failed-to-create-project',
|
||||
title: 'Failed to create project',
|
||||
id: 'error-deploying-project',
|
||||
title: 'Error deploying project',
|
||||
variant: 'error',
|
||||
onDismiss: dismiss,
|
||||
});
|
||||
|
||||
@@ -166,7 +166,7 @@ const GeneralTabPanel = () => {
|
||||
setSelectedTransferOrganization(org.org);
|
||||
setOpenTransferDialog(!openTransferDialog);
|
||||
})}
|
||||
className="self-stretch space-y-3 px-2"
|
||||
className="self-stretch space-y-3"
|
||||
>
|
||||
<Heading className="text-sky-950 text-lg font-medium leading-normal">
|
||||
Transfer project
|
||||
@@ -197,7 +197,7 @@ const GeneralTabPanel = () => {
|
||||
from={project.organization.name}
|
||||
to={selectedUserOrgName}
|
||||
/>
|
||||
<div className="self-stretch space-y-3 px-2">
|
||||
<div className="self-stretch space-y-3">
|
||||
<Heading className="text-sky-950 text-lg font-medium leading-normal">
|
||||
Delete project
|
||||
</Heading>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { EditBigIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const meta: Meta<typeof EditBigIcon> = {
|
||||
title: 'Icons/EditBigIcon',
|
||||
component: EditBigIcon,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EditBigIcon>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: ({ size, name }) => <EditBigIcon size={size} name={name} />,
|
||||
args: {
|
||||
size: '24px',
|
||||
name: 'plus',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { HideEyeOffIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const meta: Meta<typeof HideEyeOffIcon> = {
|
||||
title: 'Icons/HideEyeOffIcon',
|
||||
component: HideEyeOffIcon,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof HideEyeOffIcon>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: ({ size, name }) => <HideEyeOffIcon size={size} name={name} />,
|
||||
args: {
|
||||
size: '24px',
|
||||
name: 'plus',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ShowEyeIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const meta: Meta<typeof ShowEyeIcon> = {
|
||||
title: 'Icons/ShowEyeIcon',
|
||||
component: ShowEyeIcon,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof ShowEyeIcon>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: ({ size, name }) => <ShowEyeIcon size={size} name={name} />,
|
||||
args: {
|
||||
size: '24px',
|
||||
name: 'plus',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
User,
|
||||
Project,
|
||||
Organization,
|
||||
Role,
|
||||
OrganizationMember,
|
||||
ProjectMember,
|
||||
EnvironmentVariable,
|
||||
Deployment,
|
||||
DeploymentStatus,
|
||||
DomainStatus,
|
||||
Domain,
|
||||
Environment,
|
||||
} from 'gql-client';
|
||||
|
||||
export const user: User = {
|
||||
id: '1',
|
||||
email: 'helloworld@helloworld.com',
|
||||
isVerified: true,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
name: 'Hello World',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
gitHubToken: 'GitHub Token',
|
||||
};
|
||||
|
||||
export const organizationMember: OrganizationMember = {
|
||||
id: '1',
|
||||
member: user,
|
||||
role: Role.Owner,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const organization: Organization = {
|
||||
id: '1',
|
||||
name: 'Organization',
|
||||
slug: 'organization',
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
members: [organizationMember],
|
||||
projects: [],
|
||||
};
|
||||
|
||||
export const member: ProjectMember = {
|
||||
id: '1',
|
||||
member: user,
|
||||
permissions: [],
|
||||
isPending: false,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const environmentVariable0: EnvironmentVariable = {
|
||||
id: '1',
|
||||
key: 'API_KEY',
|
||||
value: '123456',
|
||||
environment: Environment.Development,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const environmentVariable1: EnvironmentVariable = {
|
||||
id: '2',
|
||||
key: 'API_KEY_2',
|
||||
value: '123456',
|
||||
environment: Environment.Development,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
};
|
||||
|
||||
export const domain0: Domain = {
|
||||
id: '1',
|
||||
name: 'Domain',
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
branch: 'Branch',
|
||||
status: DomainStatus.Live,
|
||||
redirectTo: null,
|
||||
};
|
||||
|
||||
export const deployment0: Deployment = {
|
||||
id: '1',
|
||||
url: 'https://deployment.com',
|
||||
status: DeploymentStatus.Ready,
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
branch: 'Branch',
|
||||
environment: Environment.Development,
|
||||
isCurrent: true,
|
||||
commitHash: 'Commit Hash',
|
||||
domain: domain0,
|
||||
commitMessage: 'Commit Message',
|
||||
createdBy: user,
|
||||
};
|
||||
|
||||
export const project: Project = {
|
||||
id: '1',
|
||||
name: 'GithubUsername-ProjectName',
|
||||
owner: user,
|
||||
deployments: [deployment0],
|
||||
repository: 'Repository',
|
||||
prodBranch: 'Prod Branch',
|
||||
description: 'Description',
|
||||
createdAt: '2021-08-01T00:00:00.000Z',
|
||||
updatedAt: '2021-08-01T00:00:00.000Z',
|
||||
framework: 'NextJS',
|
||||
environmentVariables: [environmentVariable0, environmentVariable1],
|
||||
organization: organization,
|
||||
template: 'Template',
|
||||
members: [member],
|
||||
webhooks: ['beepboop'],
|
||||
icon: 'Icon',
|
||||
subDomain: 'SubDomain',
|
||||
};
|
||||
+36
-1
@@ -21,10 +21,45 @@ const meta: Meta<typeof AddEnvironmentVariableRow> = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
} as Meta<typeof AddEnvironmentVariableRow>;
|
||||
argTypes: {
|
||||
onDelete: {
|
||||
action: 'delete',
|
||||
},
|
||||
register: {
|
||||
action: 'register',
|
||||
},
|
||||
index: {
|
||||
type: 'number',
|
||||
},
|
||||
isDeleteDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
isDeleteDisabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof AddEnvironmentVariableRow>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const DisabledDelete: Story = {
|
||||
args: {
|
||||
isDeleteDisabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const First: Story = {
|
||||
args: {
|
||||
index: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const Second: Story = {
|
||||
args: {
|
||||
index: 1,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import AddMemberDialog from 'components/projects/project/settings/AddMemberDialog';
|
||||
|
||||
const meta: Meta<typeof AddMemberDialog> = {
|
||||
title: 'Project/Settings/AddMemberDialog',
|
||||
component: AddMemberDialog,
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
open: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
handleOpen: {
|
||||
action: 'open',
|
||||
},
|
||||
handleAddMember: {
|
||||
action: 'addMember',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
open: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof AddMemberDialog>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
reactRouterParameters,
|
||||
withRouter,
|
||||
} from 'storybook-addon-remix-react-router';
|
||||
|
||||
import DeleteProjectDialog from 'components/projects/project/settings/DeleteProjectDialog';
|
||||
import { project } from '../../MockStoriesData';
|
||||
|
||||
const meta: Meta<typeof DeleteProjectDialog> = {
|
||||
title: 'Project/Settings/DeleteProjectDialog',
|
||||
component: DeleteProjectDialog,
|
||||
tags: ['autodocs'],
|
||||
decorators: [withRouter],
|
||||
parameters: {
|
||||
reactRouter: reactRouterParameters({
|
||||
location: {
|
||||
pathParams: { userId: 'me' },
|
||||
},
|
||||
routing: {
|
||||
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings',
|
||||
},
|
||||
}),
|
||||
},
|
||||
argTypes: {
|
||||
open: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
handleOpen: {
|
||||
action: 'open',
|
||||
},
|
||||
project: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
open: true,
|
||||
project: project,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DeleteProjectDialog>;
|
||||
|
||||
export const Default: Story = {};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import DisplayEnvironmentVariables from 'components/projects/project/settings/DisplayEnvironmentVariables';
|
||||
import {
|
||||
environmentVariable0,
|
||||
environmentVariable1,
|
||||
} from '../../MockStoriesData';
|
||||
import { Environment } from 'gql-client';
|
||||
|
||||
const meta: Meta<typeof DisplayEnvironmentVariables> = {
|
||||
title: 'Project/Settings/DisplayEnvironmentVariables',
|
||||
component: DisplayEnvironmentVariables,
|
||||
argTypes: {
|
||||
environment: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
variables: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
onUpdate: {
|
||||
action: 'update',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
environment: Environment.Development,
|
||||
variables: [environmentVariable0, environmentVariable1],
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DisplayEnvironmentVariables>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import EditEnvironmentVariableRow from 'components/projects/project/settings/EditEnvironmentVariableRow';
|
||||
import { environmentVariable0 } from '../../MockStoriesData';
|
||||
|
||||
const meta: Meta<typeof EditEnvironmentVariableRow> = {
|
||||
title: 'Project/Settings/EditEnvironmentVariableRow',
|
||||
component: EditEnvironmentVariableRow,
|
||||
argTypes: {
|
||||
variable: {
|
||||
control: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
onUpdate: {
|
||||
action: 'update',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
variable: environmentVariable0,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EditEnvironmentVariableRow>;
|
||||
|
||||
export const Default: Story = {};
|
||||
@@ -17,7 +17,7 @@ const meta: Meta<typeof SetupDomain> = {
|
||||
pathParams: { userId: 'me' },
|
||||
},
|
||||
routing: {
|
||||
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains/add',
|
||||
path: '/snowball-tools-1/projects/6bb3bec2-d71b-4fc0-9e32-4767f68668f4/settings/domains',
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -14,9 +14,6 @@ export default withMT({
|
||||
xxs: '400px',
|
||||
xs: '480px',
|
||||
},
|
||||
zIndex: {
|
||||
tooltip: '52',
|
||||
},
|
||||
letterSpacing: {
|
||||
tight: '-0.084px',
|
||||
},
|
||||
@@ -178,6 +175,7 @@ export default withMT({
|
||||
4.5: '1.125rem',
|
||||
},
|
||||
zIndex: {
|
||||
tooltip: '52',
|
||||
toast: '9999',
|
||||
},
|
||||
animation: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { defineConfig, PluginOption } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [react()] as PluginOption[],
|
||||
resolve: {
|
||||
alias: {
|
||||
utils: '/src/utils',
|
||||
|
||||
Vendored
+7
-2
@@ -15,7 +15,8 @@ declare enum Environment {
|
||||
declare enum DeploymentStatus {
|
||||
Building = "Building",
|
||||
Ready = "Ready",
|
||||
Error = "Error"
|
||||
Error = "Error",
|
||||
Deleting = "Deleting"
|
||||
}
|
||||
declare enum DomainStatus {
|
||||
Live = "Live",
|
||||
@@ -223,6 +224,9 @@ type RedeployToProdResponse = {
|
||||
type RollbackDeploymentResponse = {
|
||||
rollbackDeployment: boolean;
|
||||
};
|
||||
type DeleteDeploymentResponse = {
|
||||
deleteDeployment: boolean;
|
||||
};
|
||||
type AddDomainInput = {
|
||||
name: string;
|
||||
};
|
||||
@@ -270,10 +274,11 @@ declare class GQLClient {
|
||||
deleteProject(projectId: string): Promise<DeleteProjectResponse>;
|
||||
deleteDomain(domainId: string): Promise<DeleteDomainResponse>;
|
||||
rollbackDeployment(projectId: string, deploymentId: string): Promise<RollbackDeploymentResponse>;
|
||||
deleteDeployment(deploymentId: string): Promise<DeleteDeploymentResponse>;
|
||||
addDomain(projectId: string, data: AddDomainInput): Promise<AddDomainResponse>;
|
||||
getDomains(projectId: string, filter?: FilterDomainInput): Promise<GetDomainsResponse>;
|
||||
authenticateGitHub(code: string): Promise<AuthenticateGitHubResponse>;
|
||||
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
|
||||
}
|
||||
|
||||
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
|
||||
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
|
||||
|
||||
Vendored
+7
-2
@@ -15,7 +15,8 @@ declare enum Environment {
|
||||
declare enum DeploymentStatus {
|
||||
Building = "Building",
|
||||
Ready = "Ready",
|
||||
Error = "Error"
|
||||
Error = "Error",
|
||||
Deleting = "Deleting"
|
||||
}
|
||||
declare enum DomainStatus {
|
||||
Live = "Live",
|
||||
@@ -223,6 +224,9 @@ type RedeployToProdResponse = {
|
||||
type RollbackDeploymentResponse = {
|
||||
rollbackDeployment: boolean;
|
||||
};
|
||||
type DeleteDeploymentResponse = {
|
||||
deleteDeployment: boolean;
|
||||
};
|
||||
type AddDomainInput = {
|
||||
name: string;
|
||||
};
|
||||
@@ -270,10 +274,11 @@ declare class GQLClient {
|
||||
deleteProject(projectId: string): Promise<DeleteProjectResponse>;
|
||||
deleteDomain(domainId: string): Promise<DeleteDomainResponse>;
|
||||
rollbackDeployment(projectId: string, deploymentId: string): Promise<RollbackDeploymentResponse>;
|
||||
deleteDeployment(deploymentId: string): Promise<DeleteDeploymentResponse>;
|
||||
addDomain(projectId: string, data: AddDomainInput): Promise<AddDomainResponse>;
|
||||
getDomains(projectId: string, filter?: FilterDomainInput): Promise<GetDomainsResponse>;
|
||||
authenticateGitHub(code: string): Promise<AuthenticateGitHubResponse>;
|
||||
unauthenticateGithub(): Promise<UnauthenticateGitHubResponse>;
|
||||
}
|
||||
|
||||
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
|
||||
export { type AddDomainInput, type AddDomainResponse, type AddEnvironmentVariableInput, type AddEnvironmentVariablesResponse, type AddProjectInput, type AddProjectMemberInput, type AddProjectMemberResponse, type AddProjectResponse, type AuthenticateGitHubResponse, type DeleteDeploymentResponse, type DeleteDomainResponse, type DeleteProjectResponse, type Deployment, DeploymentStatus, type Domain, DomainStatus, Environment, type EnvironmentVariable, type FilterDomainInput, GQLClient, type GetDeploymentsResponse, type GetDomainsResponse, type GetEnvironmentVariablesResponse, type GetOrganizationsResponse, type GetProjectMembersResponse, type GetProjectResponse, type GetProjectsInOrganizationResponse, type GetUserResponse, type GraphQLConfig, type Organization, type OrganizationMember, type OrganizationProject, Permission, type Project, type ProjectMember, type RedeployToProdResponse, type RemoveEnvironmentVariableResponse, type RemoveProjectMemberResponse, Role, type RollbackDeploymentResponse, type SearchProjectsResponse, type UnauthenticateGitHubResponse, type UpdateDeploymentToProdResponse, type UpdateDomainInput, type UpdateDomainResponse, type UpdateEnvironmentVariableInput, type UpdateEnvironmentVariableResponse, type UpdateProjectInput, type UpdateProjectMemberInput, type UpdateProjectMemberResponse, type UpdateProjectResponse, type User };
|
||||
|
||||
Vendored
+17
@@ -334,6 +334,11 @@ mutation ($projectId: String! ,$deploymentId: String!) {
|
||||
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
|
||||
}
|
||||
`;
|
||||
var deleteDeployment = import_client2.gql`
|
||||
mutation ($deploymentId: String!) {
|
||||
deleteDeployment(deploymentId: $deploymentId)
|
||||
}
|
||||
`;
|
||||
var addDomain = import_client2.gql`
|
||||
mutation ($projectId: String!, $data: AddDomainInput!) {
|
||||
addDomain(projectId: $projectId, data: $data)
|
||||
@@ -614,6 +619,17 @@ var GQLClient = class {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
deleteDeployment(deploymentId) {
|
||||
return __async(this, null, function* () {
|
||||
const { data } = yield this.client.mutate({
|
||||
mutation: deleteDeployment,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
});
|
||||
return data;
|
||||
});
|
||||
}
|
||||
addDomain(projectId, data) {
|
||||
return __async(this, null, function* () {
|
||||
const result = yield this.client.mutate({
|
||||
@@ -681,6 +697,7 @@ var DeploymentStatus = /* @__PURE__ */ ((DeploymentStatus2) => {
|
||||
DeploymentStatus2["Building"] = "Building";
|
||||
DeploymentStatus2["Ready"] = "Ready";
|
||||
DeploymentStatus2["Error"] = "Error";
|
||||
DeploymentStatus2["Deleting"] = "Deleting";
|
||||
return DeploymentStatus2;
|
||||
})(DeploymentStatus || {});
|
||||
var DomainStatus = /* @__PURE__ */ ((DomainStatus2) => {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+17
@@ -304,6 +304,11 @@ mutation ($projectId: String! ,$deploymentId: String!) {
|
||||
rollbackDeployment(projectId: $projectId, deploymentId: $deploymentId)
|
||||
}
|
||||
`;
|
||||
var deleteDeployment = gql2`
|
||||
mutation ($deploymentId: String!) {
|
||||
deleteDeployment(deploymentId: $deploymentId)
|
||||
}
|
||||
`;
|
||||
var addDomain = gql2`
|
||||
mutation ($projectId: String!, $data: AddDomainInput!) {
|
||||
addDomain(projectId: $projectId, data: $data)
|
||||
@@ -584,6 +589,17 @@ var GQLClient = class {
|
||||
return data;
|
||||
});
|
||||
}
|
||||
deleteDeployment(deploymentId) {
|
||||
return __async(this, null, function* () {
|
||||
const { data } = yield this.client.mutate({
|
||||
mutation: deleteDeployment,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
});
|
||||
return data;
|
||||
});
|
||||
}
|
||||
addDomain(projectId, data) {
|
||||
return __async(this, null, function* () {
|
||||
const result = yield this.client.mutate({
|
||||
@@ -651,6 +667,7 @@ var DeploymentStatus = /* @__PURE__ */ ((DeploymentStatus2) => {
|
||||
DeploymentStatus2["Building"] = "Building";
|
||||
DeploymentStatus2["Ready"] = "Ready";
|
||||
DeploymentStatus2["Error"] = "Error";
|
||||
DeploymentStatus2["Deleting"] = "Deleting";
|
||||
return DeploymentStatus2;
|
||||
})(DeploymentStatus || {});
|
||||
var DomainStatus = /* @__PURE__ */ ((DomainStatus2) => {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "gql-client",
|
||||
"license": "UNLICENSED",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
|
||||
@@ -276,6 +276,17 @@ export class GQLClient {
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteDeployment (deploymentId: string): Promise<types.DeleteDeploymentResponse> {
|
||||
const { data } = await this.client.mutate({
|
||||
mutation: mutations.deleteDeployment,
|
||||
variables: {
|
||||
deploymentId
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async addDomain (projectId: string, data: types.AddDomainInput): Promise<types.AddDomainResponse> {
|
||||
const result = await this.client.mutate({
|
||||
mutation: mutations.addDomain,
|
||||
|
||||
@@ -82,6 +82,12 @@ mutation ($projectId: String! ,$deploymentId: String!) {
|
||||
}
|
||||
`;
|
||||
|
||||
export const deleteDeployment = gql`
|
||||
mutation ($deploymentId: String!) {
|
||||
deleteDeployment(deploymentId: $deploymentId)
|
||||
}
|
||||
`;
|
||||
|
||||
export const addDomain = gql`
|
||||
mutation ($projectId: String!, $data: AddDomainInput!) {
|
||||
addDomain(projectId: $projectId, data: $data)
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum DeploymentStatus {
|
||||
Building = 'Building',
|
||||
Ready = 'Ready',
|
||||
Error = 'Error',
|
||||
Deleting = 'Deleting'
|
||||
}
|
||||
|
||||
export enum DomainStatus {
|
||||
@@ -269,6 +270,10 @@ export type RollbackDeploymentResponse = {
|
||||
rollbackDeployment: boolean
|
||||
}
|
||||
|
||||
export type DeleteDeploymentResponse = {
|
||||
deleteDeployment: boolean
|
||||
}
|
||||
|
||||
export type AddDomainInput = {
|
||||
name: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user