Skip to content

Commit

Permalink
Merge pull request #993 from traPtitech/dashboard/impr-status
Browse files Browse the repository at this point in the history
dashboard: Sleepを表示 / Filter部分にカウントを表示
  • Loading branch information
motoki317 authored Jan 7, 2025
2 parents 0483a5f + b969133 commit 136bb2b
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 97 deletions.
5 changes: 5 additions & 0 deletions dashboard/src/components/templates/app/AppDeployInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ const AppDeployInfo: Component<{
'bg-color-overlay-ui-primary-to-transparency-primary-selected hover:bg-color-overlay-ui-primary-to-transparency-primary-hover',
deploymentState(props.app) === ApplicationState.Idle &&
'bg-color-overlay-ui-primary-to-black-alpha-200 hover:bg-color-overlay-ui-primary-to-black-alpha-100',
deploymentState(props.app) === ApplicationState.Sleeping &&
'bg-color-overlay-ui-primary-to-violet-200 hover:bg-color-overlay-ui-primary-to-violet-100',
deploymentState(props.app) === ApplicationState.Deploying &&
'bg-color-overlay-ui-primary-to-transparency-warn-selected hover:bg-color-overlay-ui-primary-to-transparency-warn-hover',
deploymentState(props.app) === ApplicationState.Error &&
Expand Down Expand Up @@ -158,6 +160,9 @@ const AppDeployInfo: Component<{
<Show when={deploymentState(props.app) !== ApplicationState.Running}>
<List.RowData>現在アプリが起動していないためSSHアクセスはできません</List.RowData>
</Show>
<Show when={deploymentState(props.app) === ApplicationState.Sleeping}>
<List.RowData>アプリのURLにアクセスがあった場合、自動的に起動します</List.RowData>
</Show>
</List.RowContent>
</DeployInfoContainer>
</Show>
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/components/templates/app/AppStatusIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const components: Record<ApplicationState, (size: IconProps) => JSXElement> = {
style={{ 'font-size': `${props.size}px` }}
/>
),
[ApplicationState.Sleeping]: (props) => (
<div class="i-material-symbols:mode-standby shrink-0 text-violet-400" style={{ 'font-size': `${props.size}px` }} />
),
[ApplicationState.Running]: (props) => (
<div
class="i-material-symbols:check-circle shrink-0 text-accent-success"
Expand Down
37 changes: 32 additions & 5 deletions dashboard/src/components/templates/app/AppsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { type Component, type ComponentProps, For, type Setter, Show } from 'sol
import { CheckBoxIcon } from '/@/components/UI/CheckBoxIcon'
import { RadioIcon } from '/@/components/UI/RadioIcon'
import { styled } from '/@/components/styled-components'
import { type ApplicationState, type RepositoryOrigin, originToIcon } from '/@/libs/application'
import {
type ApplicationState,
type RepoWithApp,
type RepositoryOrigin,
applicationState,
originToIcon,
repositoryURLToOrigin,
} from '/@/libs/application'
import { clsx } from '/@/libs/clsx'
import { allOrigins, allStatuses, sortItems } from '/@/pages/apps'
import { AppStatusIcon } from './AppStatusIcon'
Expand All @@ -19,6 +26,7 @@ const selectItemStyle = clsx(
const FilterItemContainer = styled('div', 'flex flex-col gap-2 text-bold text-text-black')

const AppsFilter: Component<{
allRepoWithApps: RepoWithApp[]
statuses: ApplicationState[]
setStatues: Setter<ApplicationState[]>
origin: RepositoryOrigin[]
Expand All @@ -31,6 +39,21 @@ const AppsFilter: Component<{
const filtered = () =>
props.statuses.length !== allStatuses.length || props.origin.length !== allOrigins.length || props.includeNoApp

const appCountByStatus = (status: ApplicationState): number => {
return props.allRepoWithApps
.filter((repo) => props.origin.includes(repositoryURLToOrigin(repo.repo.url)))
.flatMap((repo) => repo.apps.filter((app) => applicationState(app) === status)).length
}

const repoCountByOrigin = (origin: RepositoryOrigin): number => {
return props.allRepoWithApps
.filter((repo) => repositoryURLToOrigin(repo.repo.url) === origin)
.filter(
(repo) =>
props.includeNoApp || repo.apps.filter((app) => props.statuses.includes(applicationState(app))).length > 0,
).length
}

return (
<DropdownMenu.Root>
<DropdownMenu.Trigger
Expand Down Expand Up @@ -64,7 +87,7 @@ const AppsFilter: Component<{
}}
>
<FilterItemContainer class="grid-area-[status]">
Status
App Status
<ItemsContainer>
<For each={allStatuses}>
{(s) => (
Expand All @@ -84,15 +107,17 @@ const AppsFilter: Component<{
<CheckBoxIcon checked={props.statuses.includes(s.value)} />
</Checkbox.Indicator>
<AppStatusIcon state={s.value} hideTooltip />
{s.label}
<span>
{s.label} ({appCountByStatus(s.value)})
</span>
</Checkbox.Label>
</Checkbox.Root>
)}
</For>
</ItemsContainer>
</FilterItemContainer>
<FilterItemContainer class="grid-area-[provider]">
Origin
Repo Origin
<ItemsContainer>
<For each={allOrigins}>
{(s) => (
Expand All @@ -112,7 +137,9 @@ const AppsFilter: Component<{
<CheckBoxIcon checked={props.origin.includes(s.value)} />
</Checkbox.Indicator>
{originToIcon(s.value)}
{s.label}
<span>
{s.label} ({repoCountByOrigin(s.value)})
</span>
</Checkbox.Label>
</Checkbox.Root>
)}
Expand Down
62 changes: 60 additions & 2 deletions dashboard/src/libs/application.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { timestampDate } from '@bufbuild/protobuf/wkt'
import { AiFillGithub } from 'solid-icons/ai'
import { RiDevelopmentGitRepositoryLine } from 'solid-icons/ri'
import { SiGitea } from 'solid-icons/si'
Expand All @@ -9,6 +10,7 @@ import {
type CreateWebsiteRequest,
DeployType,
PortPublicationProtocol,
type Repository,
type Website,
} from '/@/api/neoshowcase/protobuf/gateway_pb'

Expand All @@ -25,6 +27,7 @@ export enum ApplicationState {
Idle = 'Idle',
Deploying = 'Deploying',
Running = 'Running',
Sleeping = 'Sleeping',
Serving = 'Serving',
Error = 'Error',
}
Expand All @@ -40,8 +43,8 @@ const autoShutdownEnabled = (app: Application): boolean => {
}

export const deploymentState = (app: Application): ApplicationState => {
// if app is not running or autoShutdown is enabled and container is missing, it's idle
if (!app.running || (autoShutdownEnabled(app) && app.container === Application_ContainerState.MISSING)) {
// App is not running
if (!app.running) {
return ApplicationState.Idle
}
if (app.currentBuild === '') {
Expand All @@ -51,6 +54,11 @@ export const deploymentState = (app: Application): ApplicationState => {
if (app.deployType === DeployType.RUNTIME) {
switch (app.container) {
case Application_ContainerState.MISSING:
// Has auto shutdown enabled, and the container is missing - app is sleeping, and will start on HTTP access
if (autoShutdownEnabled(app)) {
return ApplicationState.Sleeping
}
return ApplicationState.Deploying
case Application_ContainerState.STARTING:
return ApplicationState.Deploying
case Application_ContainerState.RUNNING:
Expand Down Expand Up @@ -141,3 +149,53 @@ export const portPublicationProtocolMap: Record<PortPublicationProtocol, string>
[PortPublicationProtocol.TCP]: 'TCP',
[PortPublicationProtocol.UDP]: 'UDP',
}

const newestAppDate = (apps: Application[]): number =>
Math.max(0, ...apps.map((a) => (a.updatedAt ? timestampDate(a.updatedAt).getTime() : 0)))
const compareRepoWithApp =
(sort: 'asc' | 'desc') =>
(a: RepoWithApp, b: RepoWithApp): number => {
// Sort by apps updated at
if (a.apps.length > 0 && b.apps.length > 0) {
if (sort === 'asc') {
return newestAppDate(a.apps) - newestAppDate(b.apps)
}
return newestAppDate(b.apps) - newestAppDate(a.apps)
}
// Bring up repositories with 1 or more apps at top
if ((a.apps.length > 0 && b.apps.length === 0) || (a.apps.length === 0 && b.apps.length > 0)) {
return b.apps.length - a.apps.length
}
// Fallback to sort by repository id
return a.repo.id.localeCompare(b.repo.id)
}

export interface RepoWithApp {
repo: Repository
apps: Application[]
}

export const useApplicationsFilter = (
repos: Repository[],
apps: Application[],
statuses: ApplicationState[],
origins: RepositoryOrigin[],
includeNoApp: boolean,
sort: 'asc' | 'desc',
): RepoWithApp[] => {
const filteredReposByOrigin = repos.filter((r) => origins.includes(repositoryURLToOrigin(r.url)))
const filteredApps = apps.filter((a) => statuses.includes(applicationState(a)))

const appsMap = {} as Record<string, Application[]>
for (const app of filteredApps) {
if (!appsMap[app.repositoryId]) appsMap[app.repositoryId] = []
appsMap[app.repositoryId].push(app)
}
const res = filteredReposByOrigin.reduce<RepoWithApp[]>((acc, repo) => {
if (!includeNoApp && !appsMap[repo.id]) return acc
acc.push({ repo, apps: appsMap[repo.id] || [] })
return acc
}, [])
res.sort(compareRepoWithApp(sort))
return res
}
Loading

0 comments on commit 136bb2b

Please sign in to comment.