Set record data with repo commit hash and package.json content (#65)

* Get latest commit hash from repo when adding project

* Update UI/UX

* Fill registry record with data from package.json

* Add package json type

* Correct record data based on laconic console

* Update README

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-02-14 11:03:22 +05:30 committed by GitHub
parent 76dfd3bb76
commit 9144d42f70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 203 additions and 105 deletions

View File

@ -77,21 +77,31 @@
# 0.0.0.0:32771 # 0.0.0.0:32771
``` ```
- Reserve authority for `snowball` - Reserve authorities for `snowballtools` and `cerc-io`
```bash ```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority reserve snowball" laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority reserve snowballtools"
# {"success":true} # {"success":true}
``` ```
- Set authority bond
```bash ```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority bond set snowball $BOND_ID" laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority reserve cerc-io"
# {"success":true} # {"success":true}
``` ```
- Start the server - Set authority bond for `snowballtools` and `cerc-io`
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority bond set snowballtools $BOND_ID"
# {"success":true}
```
```bash
laconic-so --stack fixturenet-laconic-loaded deploy exec cli "laconic cns authority bond set cerc-io $BOND_ID"
# {"success":true}
```
- Start the server in `packages/backend`
```bash ```bash
yarn start yarn start

View File

@ -16,8 +16,10 @@
"express": "^4.18.2", "express": "^4.18.2",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"graphql": "^16.8.1", "graphql": "^16.8.1",
"luxon": "^3.4.4",
"nanoid": "3", "nanoid": "3",
"nanoid-dictionary": "^5.0.0-beta.1", "nanoid-dictionary": "^5.0.0-beta.1",
"octokit": "^3.1.2",
"reflect-metadata": "^0.2.1", "reflect-metadata": "^0.2.1",
"semver": "^7.6.0", "semver": "^7.6.0",
"toml": "^3.0.0", "toml": "^3.0.0",

View File

@ -28,13 +28,13 @@ export enum DeploymentStatus {
export interface ApplicationRecord { export interface ApplicationRecord {
type: string; type: string;
version:string version:string
name: string name?: string
description: string description?: string
homepage: string homepage?: string
license: string license?: string
author: string author?: string
repository: string, repository?: string[],
app_version: string app_version?: string
repository_ref: string repository_ref: string
app_type: string app_type: string
} }

View File

@ -20,14 +20,8 @@ export interface ApplicationDeploymentRequest {
version: string version: string
name: string name: string
application: string application: string
config: { config: string,
env: {[key:string]: string} meta: string
},
meta: {
note: string
repository: string
repository_ref: string
}
} }
@Entity() @Entity()

View File

@ -1,17 +1,19 @@
import debug from 'debug'; import debug from 'debug';
import assert from 'assert';
import { inc as semverInc } from 'semver'; import { inc as semverInc } from 'semver';
import { DateTime } from 'luxon';
import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk'; import { Registry as LaconicRegistry } from '@cerc-io/laconic-sdk';
import { RegistryConfig } from './config'; import { RegistryConfig } from './config';
import { ApplicationDeploymentRequest } from './entity/Project'; import { ApplicationDeploymentRequest } from './entity/Project';
import { ApplicationRecord } from './entity/Deployment'; import { ApplicationRecord } from './entity/Deployment';
import { PackageJSON } from './types';
const log = debug('snowball:registry'); const log = debug('snowball:registry');
const APP_RECORD_TYPE = 'ApplicationRecord'; const APP_RECORD_TYPE = 'ApplicationRecord';
const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest'; const DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRequest';
const AUTHORITY_NAME = 'snowball';
// TODO: Move registry code to laconic-sdk/watcher-ts // TODO: Move registry code to laconic-sdk/watcher-ts
export class Registry { export class Registry {
@ -23,16 +25,21 @@ export class Registry {
this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId); this.registry = new LaconicRegistry(registryConfig.gqlEndpoint, registryConfig.restEndpoint, registryConfig.chainId);
} }
async createApplicationRecord (data: { recordName: string, appType: string }): Promise<{registryRecordId: string, registryRecordData: ApplicationRecord}> { async createApplicationRecord ({
// TODO: Get record name from repo package.json name packageJSON,
const recordName = data.recordName; commitHash,
appType
}: {
packageJSON: PackageJSON
appType: string,
commitHash: string
}): Promise<{registryRecordId: string, registryRecordData: ApplicationRecord}> {
// Use laconic-sdk to publish record // Use laconic-sdk to publish record
// Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh // Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts/publish-app-record.sh
// Fetch previous records // Fetch previous records
const records = await this.registry.queryRecords({ const records = await this.registry.queryRecords({
type: APP_RECORD_TYPE, type: APP_RECORD_TYPE,
name: recordName name: packageJSON.name
}, true); }, true);
// Get next version of record // Get next version of record
@ -40,23 +47,21 @@ export class Registry {
const [latestBondRecord] = bondRecords.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime()); const [latestBondRecord] = bondRecords.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
const nextVersion = semverInc(latestBondRecord?.attributes.version ?? '0.0.0', 'patch'); const nextVersion = semverInc(latestBondRecord?.attributes.version ?? '0.0.0', 'patch');
assert(nextVersion, 'Application record version not valid');
// Create record of type ApplicationRecord and publish // Create record of type ApplicationRecord and publish
const applicationRecord = { const applicationRecord = {
type: APP_RECORD_TYPE, type: APP_RECORD_TYPE,
version: nextVersion ?? '', version: nextVersion,
name: recordName, repository_ref: commitHash,
app_type: appType,
// TODO: Get data from repo package.json ...(packageJSON.name && { name: packageJSON.name }),
description: '', ...(packageJSON.description && { description: packageJSON.description }),
homepage: '', ...(packageJSON.homepage && { homepage: packageJSON.homepage }),
license: '', ...(packageJSON.license && { license: packageJSON.license }),
author: '', ...(packageJSON.author && { author: typeof packageJSON.author === 'object' ? JSON.stringify(packageJSON.author) : packageJSON.author }),
repository: '', ...(packageJSON.repository && { repository: [packageJSON.repository] }),
app_version: '0.1.0', ...(packageJSON.version && { app_version: packageJSON.version })
// TODO: Get latest commit hash from repo production branch / deployment
repository_ref: '10ac6678e8372a05ad5bb1c34c34',
app_type: data.appType
}; };
const result = await this.registry.setRecord( const result = await this.registry.setRecord(
@ -71,8 +76,8 @@ export class Registry {
log('Application record data:', applicationRecord); log('Application record data:', applicationRecord);
// TODO: Discuss computation of crn // TODO: Discuss computation of CRN
const crn = this.getCrn(data.recordName); const crn = this.getCrn(packageJSON.name ?? '');
await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn }, this.registryConfig.privateKey, this.registryConfig.fee);
await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee); await this.registry.setName({ cid: result.data.id, crn: `${crn}@${applicationRecord.app_version}` }, this.registryConfig.privateKey, this.registryConfig.fee);
@ -81,7 +86,14 @@ export class Registry {
return { registryRecordId: result.data.id, registryRecordData: applicationRecord }; return { registryRecordId: result.data.id, registryRecordData: applicationRecord };
} }
async createApplicationDeploymentRequest (data: { appName: string }): Promise<{registryRecordId: string, registryRecordData: ApplicationDeploymentRequest}> { async createApplicationDeploymentRequest (data: {
appName: string,
commitHash: string,
repository: string
}): Promise<{
registryRecordId: string,
registryRecordData: ApplicationDeploymentRequest
}> {
const crn = this.getCrn(data.appName); const crn = this.getCrn(data.appName);
const records = await this.registry.resolveNames([crn]); const records = await this.registry.resolveNames([crn]);
const applicationRecord = records[0]; const applicationRecord = records[0];
@ -101,16 +113,17 @@ export class Registry {
// dns: '$CERC_REGISTRY_DEPLOYMENT_SHORT_HOSTNAME', // dns: '$CERC_REGISTRY_DEPLOYMENT_SHORT_HOSTNAME',
// deployment: '$CERC_REGISTRY_DEPLOYMENT_CRN', // deployment: '$CERC_REGISTRY_DEPLOYMENT_CRN',
config: { // https://git.vdb.to/cerc-io/laconic-registry-cli/commit/129019105dfb93bebcea02fde0ed64d0f8e5983b
config: JSON.stringify({
env: { env: {
CERC_WEBAPP_DEBUG: `${applicationRecord.attributes.app_version}` CERC_WEBAPP_DEBUG: `${applicationRecord.attributes.app_version}`
} }
}, }),
meta: { meta: JSON.stringify({
note: `Added by Snowball @ ${(new Date()).toISOString()}`, note: `Added by Snowball @ ${DateTime.utc().toFormat('EEE LLL dd HH:mm:ss \'UTC\' yyyy')}`,
repository: applicationRecord.attributes.repository, repository: data.repository,
repository_ref: applicationRecord.attributes.repository_ref repository_ref: data.commitHash
} })
}; };
const result = await this.registry.setRecord( const result = await this.registry.setRecord(
@ -128,7 +141,14 @@ export class Registry {
return { registryRecordId: result.data.id, registryRecordData: applicationDeploymentRequest }; return { registryRecordId: result.data.id, registryRecordData: applicationDeploymentRequest };
} }
getCrn (appName: string): string { getCrn (packageJsonName: string): string {
return `crn://${AUTHORITY_NAME}/applications/${appName}`; const [arg1, arg2] = packageJsonName.split('/');
if (arg2) {
const authority = arg1.replace('@', '');
return `crn://${authority}/applications/${arg2}`;
}
return `crn://${arg1}/applications/${arg1}`;
} }
} }

View File

@ -1,6 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import debug from 'debug'; import debug from 'debug';
import { DeepPartial, FindOptionsWhere } from 'typeorm'; import { DeepPartial, FindOptionsWhere } from 'typeorm';
import { Octokit } from 'octokit';
import { OAuthApp } from '@octokit/oauth-app'; import { OAuthApp } from '@octokit/oauth-app';
@ -18,12 +19,12 @@ const log = debug('snowball:service');
export class Service { export class Service {
private db: Database; private db: Database;
private app: OAuthApp; private oauthApp: OAuthApp;
private registry: Registry; private registry: Registry;
constructor (db: Database, app: OAuthApp, registry: Registry) { constructor (db: Database, app: OAuthApp, registry: Registry) {
this.db = db; this.db = db;
this.app = app; this.oauthApp = app;
this.registry = registry; this.registry = registry;
} }
@ -35,6 +36,13 @@ export class Service {
}); });
} }
async getOctokit (userId: string): Promise<Octokit> {
const user = await this.db.getUser({ where: { id: userId } });
assert(user && user.gitHubToken, 'User needs to be authenticated with GitHub token');
return new Octokit({ auth: user.gitHubToken });
}
async getOrganizationsByUserId (userId: string): Promise<Organization[]> { async getOrganizationsByUserId (userId: string): Promise<Organization[]> {
const dbOrganizations = await this.db.getOrganizationsByUserId(userId); const dbOrganizations = await this.db.getOrganizationsByUserId(userId);
return dbOrganizations; return dbOrganizations;
@ -200,7 +208,10 @@ export class Service {
await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null }); await this.db.updateDeploymentById(oldCurrentDeployment.id, { isCurrent: false, domain: null });
} }
const octokit = await this.getOctokit(userId);
const newDeployement = await this.createDeployment(userId, const newDeployement = await this.createDeployment(userId,
octokit,
{ {
project: oldDeployment.project, project: oldDeployment.project,
isCurrent: true, isCurrent: true,
@ -213,10 +224,28 @@ export class Service {
return newDeployement; return newDeployement;
} }
async createDeployment (userId: string, data: DeepPartial<Deployment>): Promise<Deployment> { async createDeployment (userId: string, octokit: Octokit, data: DeepPartial<Deployment>): Promise<Deployment> {
assert(data.project?.repository, 'Project repository not found');
const [owner, repo] = data.project.repository.split('/');
const { data: packageJSONData } = await octokit.rest.repos.getContent({
owner,
repo,
path: 'package.json',
ref: data.commitHash
});
if (!packageJSONData) {
throw new Error('Package.json file not found');
}
assert(!Array.isArray(packageJSONData) && packageJSONData.type === 'file');
const packageJSON = JSON.parse(atob(packageJSONData.content));
const { registryRecordId, registryRecordData } = await this.registry.createApplicationRecord({ const { registryRecordId, registryRecordData } = await this.registry.createApplicationRecord({
recordName: data.project?.name ?? '', packageJSON,
appType: data.project?.template ?? '' appType: data.project!.template!,
commitHash: data.commitHash!
}); });
const newDeployement = await this.db.addDeployement({ const newDeployement = await this.db.addDeployement({
@ -250,24 +279,43 @@ export class Service {
const project = await this.db.addProject(userId, organization.id, data); const project = await this.db.addProject(userId, organization.id, data);
// TODO: Get repository details from github const octokit = await this.getOctokit(userId);
await this.createDeployment(userId, const [owner, repo] = project.repository.split('/');
const { data: [latestCommit] } = await octokit.rest.repos.listCommits({
owner,
repo,
sha: project.prodBranch,
per_page: 1
});
const { data: repoDetails } = await octokit.rest.repos.get({ owner, repo });
// Create deployment with prod branch and latest commit
const newDeployment = await this.createDeployment(userId,
octokit,
{ {
project, project,
isCurrent: true, isCurrent: true,
branch: project.prodBranch, branch: project.prodBranch,
environment: Environment.Production, environment: Environment.Production,
// TODO: Set latest commit hash domain: null,
commitHash: '', commitHash: latestCommit.sha
domain: null
}); });
const { registryRecordId, registryRecordData } = await this.registry.createApplicationDeploymentRequest({ appName: project.name }); const { registryRecordId, registryRecordData } = await this.registry.createApplicationDeploymentRequest(
{
appName: newDeployment.registryRecordData.name!,
commitHash: latestCommit.sha,
repository: repoDetails.git_url
});
await this.db.updateProjectById(project.id, { await this.db.updateProjectById(project.id, {
registryRecordId, registryRecordId,
registryRecordData registryRecordData
}); });
// TODO: Setup repo webhook for push events
return project; return project;
} }
@ -311,7 +359,10 @@ export class Service {
await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false }); await this.db.updateDeploymentById(deploymentId, { domain: null, isCurrent: false });
const octokit = await this.getOctokit(userId);
const newDeployement = await this.createDeployment(userId, const newDeployement = await this.createDeployment(userId,
octokit,
{ {
project: oldDeployment.project, project: oldDeployment.project,
// TODO: Put isCurrent field in project // TODO: Put isCurrent field in project
@ -435,7 +486,7 @@ export class Service {
} }
async authenticateGitHub (code:string, userId: string): Promise<{token: string}> { async authenticateGitHub (code:string, userId: string): Promise<{token: string}> {
const { authentication: { token } } = await this.app.createToken({ const { authentication: { token } } = await this.oauthApp.createToken({
code code
}); });

View File

@ -0,0 +1,9 @@
export interface PackageJSON {
name?: string;
version?: string;
author?: string;
description?: string;
homepage?: string;
license?: string;
repository?: string;
}

View File

@ -10,7 +10,7 @@
"registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "qbafyrehvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-ffhae3zq.snowball.xyz" "url": "testProject-ffhae3zq.snowball.xyz"
}, },
{ {
@ -24,7 +24,7 @@
"registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "wbafyreihvzya6ovp4yfpkqnddkui2iw7thbhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-vehagei8.snowball.xyz" "url": "testProject-vehagei8.snowball.xyz"
}, },
{ {
@ -38,7 +38,7 @@
"registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ebafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-qmgekyte.snowball.xyz" "url": "testProject-qmgekyte.snowball.xyz"
}, },
{ {
@ -52,7 +52,7 @@
"registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi", "registryRecordId": "rbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhw74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "prod", "branch": "prod",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-f8wsyim6.snowball.xyz" "url": "testProject-f8wsyim6.snowball.xyz"
}, },
{ {
@ -66,7 +66,7 @@
"registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "tbafyreihvzya6ovp4yfpqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-2-eO8cckxk.snowball.xyz" "url": "testProject-2-eO8cckxk.snowball.xyz"
}, },
{ {
@ -80,7 +80,7 @@
"registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ybafyreihvzya6ovp4yfpkqnddkui2iw7t6bhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-2-yaq0t5yw.snowball.xyz" "url": "testProject-2-yaq0t5yw.snowball.xyz"
}, },
{ {
@ -94,7 +94,7 @@
"registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi", "registryRecordId": "ubafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "testProject-2-hwwr6sbx.snowball.xyz" "url": "testProject-2-hwwr6sbx.snowball.xyz"
}, },
{ {
@ -108,7 +108,7 @@
"registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "ibayreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "main", "branch": "main",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "iglootools-ndxje48a.snowball.xyz" "url": "iglootools-ndxje48a.snowball.xyz"
}, },
{ {
@ -122,7 +122,7 @@
"registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi", "registryRecordId": "obafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowoi",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "iglootools-gtgpgvei.snowball.xyz" "url": "iglootools-gtgpgvei.snowball.xyz"
}, },
{ {
@ -136,7 +136,7 @@
"registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo", "registryRecordId": "pbafyreihvzya6ovp4yfpkqnddkui2iw7t6hbhwq74lbqs7bhobvmfhrowo",
"registryRecordData": {}, "registryRecordData": {},
"branch": "test", "branch": "test",
"commitHash": "testXyz", "commitHash": "d5dfd7b827226b0d09d897346d291c256e113e00",
"url": "iglootools-b4bpthjr.snowball.xyz" "url": "iglootools-b4bpthjr.snowball.xyz"
} }
] ]

View File

@ -3,10 +3,10 @@
"ownerIndex": 0, "ownerIndex": 0,
"organizationIndex": 0, "organizationIndex": 0,
"name": "testProject", "name": "testProject",
"repository": "test", "repository": "snowball-tools/snowball-ts-framework-template",
"prodBranch": "main", "prodBranch": "main",
"description": "test", "description": "test",
"template": "test", "template": "webapp",
"framework": "test", "framework": "test",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
@ -18,10 +18,10 @@
"ownerIndex": 1, "ownerIndex": 1,
"organizationIndex": 0, "organizationIndex": 0,
"name": "testProject-2", "name": "testProject-2",
"repository": "test-2", "repository": "snowball-tools/snowball-ts-framework-template",
"prodBranch": "main", "prodBranch": "main",
"description": "test-2", "description": "test-2",
"template": "test-2", "template": "webapp",
"framework": "test-2", "framework": "test-2",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
@ -33,10 +33,10 @@
"ownerIndex": 2, "ownerIndex": 2,
"organizationIndex": 0, "organizationIndex": 0,
"name": "iglootools", "name": "iglootools",
"repository": "test-3", "repository": "snowball-tools/snowball-ts-framework-template",
"prodBranch": "main", "prodBranch": "main",
"description": "test-3", "description": "test-3",
"template": "test-3", "template": "webapp",
"framework": "test-3", "framework": "test-3",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
@ -48,10 +48,10 @@
"ownerIndex": 1, "ownerIndex": 1,
"organizationIndex": 0, "organizationIndex": 0,
"name": "iglootools-2", "name": "iglootools-2",
"repository": "test-4", "repository": "snowball-tools/snowball-ts-framework-template",
"prodBranch": "main", "prodBranch": "main",
"description": "test-4", "description": "test-4",
"template": "test-4", "template": "webapp",
"framework": "test-4", "framework": "test-4",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",
@ -63,10 +63,10 @@
"ownerIndex": 0, "ownerIndex": 0,
"organizationIndex": 1, "organizationIndex": 1,
"name": "snowball-2", "name": "snowball-2",
"repository": "test-5", "repository": "snowball-tools/snowball-ts-framework-template",
"prodBranch": "main", "prodBranch": "main",
"description": "test-5", "description": "test-5",
"template": "test-5", "template": "webapp",
"framework": "test-5", "framework": "test-5",
"webhooks": [], "webhooks": [],
"icon": "", "icon": "",

View File

@ -7,6 +7,7 @@ import {
MenuList, MenuList,
MenuItem, MenuItem,
Typography, Typography,
Avatar,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import { relativeTimeISO } from '../../utils/time'; import { relativeTimeISO } from '../../utils/time';
@ -20,7 +21,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
return ( return (
<div className="bg-white border border-gray-200 rounded-lg shadow"> <div className="bg-white border border-gray-200 rounded-lg shadow">
<div className="flex gap-2 p-2 items-center"> <div className="flex gap-2 p-2 items-center">
<div>{project.icon}</div> <Avatar variant="square" src={project.icon} />
<div className="grow"> <div className="grow">
<Link to={`projects/${project.id}`}> <Link to={`projects/${project.id}`}>
<Typography>{project.name}</Typography> <Typography>{project.name}</Typography>
@ -42,10 +43,10 @@ const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
</div> </div>
<div className="border-t-2 border-solid p-4 bg-gray-50"> <div className="border-t-2 border-solid p-4 bg-gray-50">
<Typography variant="small" color="gray"> <Typography variant="small" color="gray">
{project.latestCommit.message} ^ {project.latestCommit.message}
</Typography> </Typography>
<Typography variant="small" color="gray"> <Typography variant="small" color="gray">
{relativeTimeISO(project.latestCommit.createdAt)} on{' '} {relativeTimeISO(project.latestCommit.createdAt)} on ^&nbsp;
{project.latestCommit.branch} {project.latestCommit.branch}
</Typography> </Typography>
</div> </div>

View File

@ -9,6 +9,7 @@ import {
ListItemPrefix, ListItemPrefix,
Card, Card,
Typography, Typography,
Avatar,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import SearchBar from '../SearchBar'; import SearchBar from '../SearchBar';
@ -86,7 +87,7 @@ const ProjectSearchBar = ({ onChange }: ProjectsSearchProps) => {
{...getItemProps({ item, index })} {...getItemProps({ item, index })}
> >
<ListItemPrefix> <ListItemPrefix>
<i>^</i> <Avatar src={item.icon} variant="square" />
</ListItemPrefix> </ListItemPrefix>
<div> <div>
<Typography variant="h6" color="blue-gray"> <Typography variant="h6" color="blue-gray">

View File

@ -32,8 +32,9 @@ const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => {
<div> <div>
<p>Connect to your git account</p> <p>Connect to your git account</p>
<p> <p>
Once connected, you can create projects by importing repositories Once connected, you can import a repository from your
under the account <br />
account or start with one of our templates.
</p> </p>
</div> </div>
<div className="mt-2 flex"> <div className="mt-2 flex">

View File

@ -22,12 +22,12 @@ const AssignDomainDialog = ({ open, handleOpen }: AssignDomainProps) => {
<DialogBody> <DialogBody>
In order to assign a domain to your production deployments, configure it In order to assign a domain to your production deployments, configure it
in the{' '} in the{' '}
{/* TODO: Navigate to settings tab panel after clicking on project settings */} <Link to="../settings/domains" className="text-light-blue-800 inline">
<Link to="" className="text-light-blue-800 inline">
project settings{' '} project settings{' '}
</Link> </Link>
(recommended). If you want to assign to this specific deployment, (recommended). If you want to assign to this specific deployment,
however, you can do so using our command-line interface: however, you can do so using our command-line interface:
{/* https://github.com/rajinwonderland/react-code-blocks/issues/138 */}
<CopyBlock <CopyBlock
text="snowball alias <deployment> <domain>" text="snowball alias <deployment> <domain>"
language="" language=""

View File

@ -18,6 +18,7 @@ import DeploymentDialogBodyCard from './DeploymentDialogBodyCard';
import AssignDomainDialog from './AssignDomainDialog'; import AssignDomainDialog from './AssignDomainDialog';
import { DeploymentDetails } from '../../../../types/project'; import { DeploymentDetails } from '../../../../types/project';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../context/GQLClientContext';
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
interface DeployDetailsCardProps { interface DeployDetailsCardProps {
deployment: DeploymentDetails; deployment: DeploymentDetails;
@ -101,12 +102,13 @@ const DeploymentDetailsCard = ({
<div className="col-span-1"> <div className="col-span-1">
<Typography color="gray">^ {deployment.branch}</Typography> <Typography color="gray">^ {deployment.branch}</Typography>
<Typography color="gray"> <Typography color="gray">
^ {deployment.commitHash} {deployment.commit.message} ^ {deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commit.message}
</Typography> </Typography>
</div> </div>
<div className="col-span-1 flex items-center"> <div className="col-span-1 flex items-center">
<Typography color="gray" className="grow"> <Typography color="gray" className="grow">
{relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name} ^ {relativeTimeMs(deployment.createdAt)} ^ {deployment.createdBy.name}
</Typography> </Typography>
<Menu placement="bottom-start"> <Menu placement="bottom-start">
<MenuHandler> <MenuHandler>

View File

@ -4,6 +4,7 @@ import { Typography, Chip, Card } from '@material-tailwind/react';
import { color } from '@material-tailwind/react/types/components/chip'; import { color } from '@material-tailwind/react/types/components/chip';
import { DeploymentDetails } from '../../../../types/project'; import { DeploymentDetails } from '../../../../types/project';
import { relativeTimeMs } from '../../../../utils/time'; import { relativeTimeMs } from '../../../../utils/time';
import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants';
interface DeploymentDialogBodyCardProps { interface DeploymentDialogBodyCardProps {
deployment: DeploymentDetails; deployment: DeploymentDetails;
@ -31,7 +32,8 @@ const DeploymentDialogBodyCard = ({
{deployment.url} {deployment.url}
</Typography> </Typography>
<Typography variant="small"> <Typography variant="small">
^ {deployment.branch} ^ {deployment.commitHash}{' '} ^ {deployment.branch} ^{' '}
{deployment.commitHash.substring(0, SHORT_COMMIT_HASH_LENGTH)}{' '}
{deployment.commit.message} {deployment.commit.message}
</Typography> </Typography>
<Typography variant="small"> <Typography variant="small">

View File

@ -8,3 +8,5 @@ export const ORGANIZATION_ID = '2379cf1f-a232-4ad2-ae14-4d881131cc26';
export const GIT_TEMPLATE_LINK = export const GIT_TEMPLATE_LINK =
'https://git.vdb.to/cerc-io/test-progressive-web-app'; 'https://git.vdb.to/cerc-io/test-progressive-web-app';
export const SHORT_COMMIT_HASH_LENGTH = 8;

View File

@ -30,9 +30,10 @@ const Import = () => {
<div>^</div> <div>^</div>
<div className="grow">{repoName}</div> <div className="grow">{repoName}</div>
</div> </div>
<div className="w-5/6 p-6">
<Deploy /> <Deploy />
</div> </div>
</div>
); );
}; };

View File

@ -1,6 +1,8 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Outlet, useLocation, useSearchParams } from 'react-router-dom'; import { Outlet, useLocation, useSearchParams } from 'react-router-dom';
import { Avatar } from '@material-tailwind/react';
import Stepper from '../../../../components/Stepper'; import Stepper from '../../../../components/Stepper';
import templateDetails from '../../../../assets/templates.json'; import templateDetails from '../../../../assets/templates.json';
import { GIT_TEMPLATE_LINK } from '../../../../constants'; import { GIT_TEMPLATE_LINK } from '../../../../constants';
@ -29,12 +31,12 @@ const CreateWithTemplate = () => {
return ( return (
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6"> <div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6 items-center">
<div>^</div> <Avatar variant="square" />
<div className="grow">{template?.name}</div> <div className="grow px-2">{template?.name}</div>
<div> <div>
<a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer"> <a href={GIT_TEMPLATE_LINK} target="_blank" rel="noreferrer">
cerc-io/test-progressive-web-app ^ cerc-io/test-progressive-web-app
</a> </a>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { Domain, DomainStatus } from 'gql-client'; import { Domain, DomainStatus } from 'gql-client';
import { useOutletContext } from 'react-router-dom'; import { useOutletContext } from 'react-router-dom';
import { Typography, Button, Chip } from '@material-tailwind/react'; import { Typography, Button, Chip, Avatar } from '@material-tailwind/react';
import ActivityCard from '../../../../components/projects/project/ActivityCard'; import ActivityCard from '../../../../components/projects/project/ActivityCard';
import { relativeTimeMs } from '../../../../utils/time'; import { relativeTimeMs } from '../../../../utils/time';
@ -96,7 +96,7 @@ const OverviewTabPanel = () => {
<div className="grid grid-cols-5"> <div className="grid grid-cols-5">
<div className="col-span-3 p-2"> <div className="col-span-3 p-2">
<div className="flex items-center gap-2 p-2 "> <div className="flex items-center gap-2 p-2 ">
<div>^</div> <Avatar src={project.icon} variant="square" />
<div className="grow"> <div className="grow">
<Typography>{project.name}</Typography> <Typography>{project.name}</Typography>
<Typography variant="small" color="gray"> <Typography variant="small" color="gray">
@ -137,7 +137,7 @@ const OverviewTabPanel = () => {
<> <>
<div className="flex justify-between p-2 text-sm"> <div className="flex justify-between p-2 text-sm">
<p>^ Source</p> <p>^ Source</p>
<p>{project.deployments[0]?.branch}</p> <p>^ {project.deployments[0]?.branch}</p>
</div> </div>
<div className="flex justify-between p-2 text-sm"> <div className="flex justify-between p-2 text-sm">
<p>^ Deployment</p> <p>^ Deployment</p>