mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-09-11 15:26:19 +03:00
Fixes various bugs in the web app
This commit is contained in:
parent
023478498c
commit
03c2f685fb
@ -26,7 +26,11 @@ body {
|
||||
}
|
||||
input,
|
||||
textarea {
|
||||
@apply relative w-full cursor-default rounded-lg bg-white py-3 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-sky-300;
|
||||
@apply relative w-full rounded-lg bg-white py-3 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-sky-300;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
textarea {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { LoginForm } from "@wasp/auth/forms/Login";
|
||||
import { Header } from "../components/Header";
|
||||
|
||||
export function LoginPage() {
|
||||
return (
|
||||
<div className="big-box">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<>
|
||||
<Header />
|
||||
<div className="big-box">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -107,7 +107,6 @@ const MainPage = () => {
|
||||
value={appName}
|
||||
onChange={(e) => setAppNameIfValid(e.target.value)}
|
||||
disabled={currentStatus.status === "inProgress"}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -124,7 +123,6 @@ Based on it, our AI code agent will then generate a full stack web app in Wasp,
|
||||
cols="50"
|
||||
onChange={(e) => setAppDesc(e.target.value)}
|
||||
disabled={currentStatus.status === "inProgress"}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 gap-3">
|
||||
|
@ -10,6 +10,7 @@ import ParentSize from "@visx/responsive/lib/components/ParentSize";
|
||||
import { poolOfExampleIdeas } from "../examples";
|
||||
import logout from "@wasp/auth/logout";
|
||||
import { WaspIcon } from "../components/WaspIcon";
|
||||
import { Header } from "../components/Header";
|
||||
|
||||
export function Stats() {
|
||||
const [filterOutExampleApps, setFilterOutExampleApps] = useState(false);
|
||||
@ -101,150 +102,154 @@ export function Stats() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="big-box">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h1 className="text-3xl font-semibold text-slate-800">Stats</h1>
|
||||
<div>
|
||||
<button className="button sm" onClick={logout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading && <p>Loading...</p>}
|
||||
|
||||
{error && <p>Error: {error.message}</p>}
|
||||
|
||||
{stats && stats.projects.length === 0 && (
|
||||
<p className="text-sm text-slate-500">No projects created yet.</p>
|
||||
)}
|
||||
|
||||
{stats && stats.projects.length > 0 && (
|
||||
<>
|
||||
<p className="text-sm text-slate-500 mb-2">
|
||||
Number of projects created in the last 24 hours:{" "}
|
||||
</p>
|
||||
<div style={{ height: 300, width: "100%" }} className="mb-4">
|
||||
<ParentSize>
|
||||
{({ width, height }) => (
|
||||
<BarChart
|
||||
projects={filteredStats}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
</ParentSize>
|
||||
<>
|
||||
<Header />
|
||||
<div className="big-box">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h1 className="text-3xl font-semibold text-slate-800">Stats</h1>
|
||||
<div>
|
||||
<button className="button sm" onClick={logout}>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="py-2 flex justify-between items-center">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
id="filter"
|
||||
type="checkbox"
|
||||
checked={filterOutExampleApps}
|
||||
onChange={(event) =>
|
||||
setFilterOutExampleApps(event.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-sky-600 bg-gray-100 border-gray-300 rounded focus:ring-sky-500"
|
||||
/>
|
||||
<label
|
||||
htmlFor="filter"
|
||||
className="ml-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Filter out example apps
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
id="default-checkbox"
|
||||
type="checkbox"
|
||||
checked={filterOutKnownUsers}
|
||||
onChange={(event) =>
|
||||
setFilterOutKnownUsers(event.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-sky-600 bg-gray-100 border-gray-300 rounded focus:ring-sky-500"
|
||||
/>
|
||||
<label
|
||||
htmlFor="default-checkbox"
|
||||
className="ml-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Filter out known users
|
||||
</label>
|
||||
</div>
|
||||
{isLoading && <p>Loading...</p>}
|
||||
|
||||
{error && <p>Error: {error.message}</p>}
|
||||
|
||||
{stats && stats.projects.length === 0 && (
|
||||
<p className="text-sm text-slate-500">No projects created yet.</p>
|
||||
)}
|
||||
|
||||
{stats && stats.projects.length > 0 && (
|
||||
<>
|
||||
<p className="text-sm text-slate-500 mb-2">
|
||||
Number of projects created in the last 24 hours:{" "}
|
||||
</p>
|
||||
<div style={{ height: 300, width: "100%" }} className="mb-4">
|
||||
<ParentSize>
|
||||
{({ width, height }) => (
|
||||
<BarChart
|
||||
projects={filteredStats}
|
||||
width={width}
|
||||
height={height}
|
||||
/>
|
||||
)}
|
||||
</ParentSize>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-slate-500">
|
||||
Number of displayed apps: {filteredStats.length}
|
||||
</p>
|
||||
</div>
|
||||
<div className="py-2 flex justify-between items-center">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
id="filter"
|
||||
type="checkbox"
|
||||
checked={filterOutExampleApps}
|
||||
onChange={(event) =>
|
||||
setFilterOutExampleApps(event.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-sky-600 bg-gray-100 border-gray-300 rounded focus:ring-sky-500"
|
||||
/>
|
||||
<label
|
||||
htmlFor="filter"
|
||||
className="ml-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Filter out example apps
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
id="default-checkbox"
|
||||
type="checkbox"
|
||||
checked={filterOutKnownUsers}
|
||||
onChange={(event) =>
|
||||
setFilterOutKnownUsers(event.target.checked)
|
||||
}
|
||||
className="w-4 h-4 text-sky-600 bg-gray-100 border-gray-300 rounded focus:ring-sky-500"
|
||||
/>
|
||||
<label
|
||||
htmlFor="default-checkbox"
|
||||
className="ml-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Filter out known users
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<table className="w-full text-sm text-left text-slate-500">
|
||||
<thead className="text-xs text-slate-700 uppercase bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
App Name
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Created At
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Time in Queue → Build
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredStats.map((stat) => (
|
||||
<tr className="bg-white border-b" key={stat.id}>
|
||||
<th
|
||||
scope="row"
|
||||
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center gap-2"
|
||||
>
|
||||
<Color value={getColorValue(stat.primaryColor)} />{" "}
|
||||
<span title={stat.description}>{stat.name}</span>{" "}
|
||||
{stat.user && (
|
||||
<span
|
||||
className="text-slate-300"
|
||||
title={stat.user.email}
|
||||
>
|
||||
<WaspIcon className="w-5 h-5" />
|
||||
</span>
|
||||
)}
|
||||
<p className="text-sm text-slate-500">
|
||||
Number of displayed apps: {filteredStats.length}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
|
||||
<table className="w-full text-sm text-left text-slate-500">
|
||||
<thead className="text-xs text-slate-700 uppercase bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
App Name
|
||||
</th>
|
||||
<td className="px-6 py-4">
|
||||
<StatusPill status={getStatusName(stat.status)} sm>
|
||||
{getStatusText(stat.status)}
|
||||
</StatusPill>
|
||||
</td>
|
||||
<td
|
||||
className="px-6 py-4"
|
||||
title={`${stat.createdAt.toLocaleDateString()} ${stat.createdAt.toLocaleTimeString()}`}
|
||||
>
|
||||
{format(stat.createdAt)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{getWaitingInQueueDuration(stat)} → {getDuration(stat)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<Link
|
||||
to={`/result/${stat.id}`}
|
||||
className="font-medium text-sky-600 hover:underline"
|
||||
>
|
||||
View the app →
|
||||
</Link>
|
||||
</td>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Status
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Created At
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3">
|
||||
Time in Queue → Build
|
||||
</th>
|
||||
<th scope="col" className="px-6 py-3"></th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredStats.map((stat) => (
|
||||
<tr className="bg-white border-b" key={stat.id}>
|
||||
<th
|
||||
scope="row"
|
||||
className="px-6 py-4 font-medium text-gray-900 whitespace-nowrap flex items-center gap-2"
|
||||
>
|
||||
<Color value={getColorValue(stat.primaryColor)} />{" "}
|
||||
<span title={stat.description}>{stat.name}</span>{" "}
|
||||
{stat.user && (
|
||||
<span
|
||||
className="text-slate-300"
|
||||
title={stat.user.email}
|
||||
>
|
||||
<WaspIcon className="w-5 h-5" />
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
<td className="px-6 py-4">
|
||||
<StatusPill status={getStatusName(stat.status)} sm>
|
||||
{getStatusText(stat.status)}
|
||||
</StatusPill>
|
||||
</td>
|
||||
<td
|
||||
className="px-6 py-4"
|
||||
title={`${stat.createdAt.toLocaleDateString()} ${stat.createdAt.toLocaleTimeString()}`}
|
||||
>
|
||||
{format(stat.createdAt)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{getWaitingInQueueDuration(stat)} →{" "}
|
||||
{getDuration(stat)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<Link
|
||||
to={`/result/${stat.id}`}
|
||||
className="font-medium text-sky-600 hover:underline"
|
||||
>
|
||||
View the app →
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { generateAppJob } from "@wasp/jobs/generateAppJob.js";
|
||||
import { log } from "./utils.js";
|
||||
|
||||
const maxProjectsInProgress = process.env.MAX_PROJECTS_IN_PROGRESS
|
||||
? parseInt(process.env.MAX_PROJECTS_IN_PROGRESS, 10)
|
||||
@ -12,7 +13,7 @@ export async function checkForPendingApps(
|
||||
};
|
||||
}
|
||||
) {
|
||||
console.log("Checking for pending apps");
|
||||
log("Checking for pending apps");
|
||||
const { Project } = context.entities;
|
||||
|
||||
const pendingProjects = await Project.findMany({
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { log } from "./utils.js";
|
||||
|
||||
export async function failStaleGenerations(
|
||||
_args: void,
|
||||
context: {
|
||||
@ -8,7 +10,7 @@ export async function failStaleGenerations(
|
||||
}
|
||||
) {
|
||||
// If a generation has been in progress for > 5 minutes, it fails it
|
||||
console.log("Failing stale generations");
|
||||
log("Failing stale generations");
|
||||
const { Project, Log } = context.entities;
|
||||
|
||||
const now = getNowInUTC();
|
||||
@ -26,9 +28,19 @@ export async function failStaleGenerations(
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
logs: {
|
||||
select: {
|
||||
id: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const project of staleProjects) {
|
||||
if (project.logs.length === 0) {
|
||||
continue;
|
||||
}
|
||||
await Project.update({
|
||||
where: { id: project.id },
|
||||
data: { status: "cancelled" },
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { spawn } from "child_process";
|
||||
import { Mutex } from "async-mutex";
|
||||
import { log } from "./utils.js";
|
||||
|
||||
const appGenerationResults: Record<string, any> = {};
|
||||
|
||||
@ -13,8 +14,7 @@ export async function generateApp(
|
||||
};
|
||||
}
|
||||
) {
|
||||
// Given the appID, it generates the app
|
||||
console.log("Generating app");
|
||||
log("Generating app");
|
||||
const appId = args.appId;
|
||||
|
||||
const { Project, Log, File } = context.entities;
|
||||
|
10
wasp-ai/src/server/jobs/utils.ts
Normal file
10
wasp-ai/src/server/jobs/utils.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export function log(line: string): void {
|
||||
const date = formatDate(new Date());
|
||||
console.log(`[${date}] ${line}`);
|
||||
}
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
return `${date.getDate()}-${
|
||||
date.getMonth() + 1
|
||||
}-${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||
}
|
Loading…
Reference in New Issue
Block a user