diff --git a/RELEASE.md b/RELEASE.md
index 9cb1002..48dcc16 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,5 +1,5 @@
-## v0.3.14 - in progress - updated 2023-12-30
+## v0.3.14 - in progress - updated 2024-01-07
### platform
* [done] improve API integration with galaxies [v0.3.1]
* [done] document multiverse API integration [starpeace-server-multiverse-api]
@@ -34,7 +34,7 @@
* [done] fixed galaxy and research menu bugs [v0.3.13]
* [done] building inspect API [v0.3.13]
* [done] player and building event handling [v0.3.13]
-* [in progress] admin API [v0.3.14]
+* [done] admin API [v0.3.14]
### assets
* [done] finish initial language translation for assets [v0.3.1]
diff --git a/assets/stylesheets/starpeace-bulma.sass b/assets/stylesheets/starpeace-bulma.sass
index 6438c6b..8f7e0d3 100644
--- a/assets/stylesheets/starpeace-bulma.sass
+++ b/assets/stylesheets/starpeace-bulma.sass
@@ -129,8 +129,10 @@ html
background: linear-gradient(to bottom, $sp-primary-bg, #000)
color: $sp-primary
+ &.sp-dark-background
+ background: $sp-dark-bg
+
&.sp-menu-background
- //background: linear-gradient(to bottom, darken($sp-primary-bg, 2.5%), darken($sp-primary-bg, 25%)) !important
background: darken($sp-dark-bg, 12.5%)
a
@@ -221,3 +223,63 @@ html
&:active,
&.is-active
background-color: lighten($sp-dark-bg, 15%)
+
+.menu
+ &.is-starpeace
+ p
+ color: $sp-light
+
+ &:not(:first-child)
+ margin-top: 1.5rem
+
+ a
+ &.is-active
+ background-color: $sp-light-bg
+
+ &:hover
+ background-color: $sp-light-bg
+ color: #fff !important
+
+
+.tag
+ &.is-starpeace
+ &.is-primary
+ background-color: $sp-primary
+
+.table
+ &.is-starpeace
+ background-color: transparent
+ border-color: $sp-dark-bg
+
+ tr
+ &:not(:last-child)
+ th,
+ td
+ border-bottom: 1px solid $sp-dark-bg
+
+ tr
+ &.nowrap
+ th
+ white-space: nowrap
+
+ th,
+ td
+ border-color: $sp-dark-bg
+ color: $sp-primary
+ vertical-align: middle
+
+ &.sp-striped
+ tr:nth-child(even)
+ td
+ background-color: darken($sp-dark-bg, 10%)
+
+ &.sp-solid-header
+ th
+ background-color: $sp-dark-bg
+ color: $sp-light
+ z-index: 500
+
+ &.sp-sticky-header
+ th
+ position: sticky
+ top: 0
diff --git a/assets/stylesheets/starpeace-inspect.sass b/assets/stylesheets/starpeace-inspect.sass
index 1a5647a..678da00 100644
--- a/assets/stylesheets/starpeace-inspect.sass
+++ b/assets/stylesheets/starpeace-inspect.sass
@@ -56,6 +56,7 @@
th
background-color: $sp-dark-bg
color: $sp-light
+ z-index: 500
&.sp-sticky-header
th
@@ -72,6 +73,9 @@
position: relative
overflow: hidden
+ .loading-container
+ grid-row: start-tabs / end-details
+
.inspect-tabs
grid-column: 1 / 2
grid-row: start-tabs / end-tabs
diff --git a/components/admin/buildings/buildings.vue b/components/admin/buildings/buildings.vue
new file mode 100644
index 0000000..5c6b9a4
--- /dev/null
+++ b/components/admin/buildings/buildings.vue
@@ -0,0 +1,197 @@
+
+.card.is-starpeace
+ .card-header
+ .card-header-title Town Buildings
+ .card-content.sp-menu-background.columns.is-marginless.is-paddingless
+ .column.is-paddingless
+ .columns.columns.is-marginless.is-paddingless
+ .column.is-narrow
+ aside.menu.is-starpeace.planet-selection
+ ul.menu-list
+ li(v-for='planet in planets')
+ a(:class="{'is-active': planetId == planet.id}" @click.stop.prevent='togglePlanet(planet.id)')
+ span {{ planet.name }}
+
+ aside.menu.is-starpeace.town-selection.mt-6
+ ul.menu-list
+ li(v-for='town in towns')
+ a(:class="{'is-active': townId == town.id}" @click.stop.prevent='toggleTown(town.id)')
+ span {{ town.name }}
+
+ .column
+ table.table.is-fullwidth.is-starpeace
+ thead
+ tr.nowrap
+ th ID
+ th Name
+ th Definition ID
+ th Tycoon ID
+ th Corporation ID
+ th Company ID
+ th.has-text-right X
+ th.has-text-right Y
+ th.has-text-right Level
+ th Upgrading
+ th.has-text-right Started At
+ th.has-text-right Finished At
+ th.has-text-right Condemned At
+ th.has-text-centered
+ tbody
+ template(v-if='!buildings.length')
+ tr
+ td.has-text-centered(colspan=4) None
+ template(v-else)
+ tr(v-for='building in buildings')
+ td {{ building.id }}
+ td {{ building.name }}
+ td {{ building.definitionId }}
+ td {{ building.tycoonId }}
+ td {{ building.corporationId }}
+ td {{ building.companyId }}
+ td.has-text-right {{ building.mapX }}
+ td.has-text-right {{ building.mapY }}
+ td.has-text-right {{ building.level }}
+ td {{ building.upgrading }}
+ td.has-text-right {{ building.constructionStartedAt?.toFormat('yyyy-MM-dd HH:mm') }}
+ td.has-text-right {{ building.constructionFinishedAt?.toFormat('yyyy-MM-dd HH:mm') }}
+ td.has-text-right {{ building.condemnedAt?.toFormat('yyyy-MM-dd HH:mm') }}
+ td.has-text-centered
+ button.button.is-starpeace(:disabled="building.corporationId == 'IFEL'" @click.stop.prevent='demolishBuilding(building.id)') Demolish
+
+
+
+
+
+
diff --git a/components/admin/dashboard/dashboard.vue b/components/admin/dashboard/dashboard.vue
new file mode 100644
index 0000000..f32438a
--- /dev/null
+++ b/components/admin/dashboard/dashboard.vue
@@ -0,0 +1,133 @@
+
+.card.is-starpeace
+ .card-header
+ .card-header-title Admin Dashboard
+ .card-content.sp-menu-background.columns.is-marginless.is-paddingless
+ .column.is-paddingless
+ .columns.columns.is-marginless.is-paddingless
+ .column.is-narrow
+ .planet-container.is-flex.is-flex-direction-column(v-for='planet in planets')
+ span.name.p-2 {{ planet.name }}
+ span.p-2.is-flex.is-flex-direction-column
+ div
+ span.sp-kv-key ID:
+ span.sp-kv-value {{ planet.id }}
+ div
+ span.sp-kv-key Name:
+ span.sp-kv-value {{ planet.name }}
+ div
+ span.sp-kv-key Enabled:
+ span.sp-kv-value {{ planet.enabled ? 'Yes' : 'No' }}
+ div
+ span.sp-kv-key Type:
+ span.sp-kv-value {{ planet.planetType }}
+ div
+ span.sp-kv-key Width:
+ span.sp-kv-value {{ planet.planetWidth }}
+ div
+ span.sp-kv-key Height:
+ span.sp-kv-value {{ planet.planetHeight }}
+ div
+ span.sp-kv-key Map ID:
+ span.sp-kv-value {{ planet.mapId }}
+ div
+ span.sp-kv-key Population:
+ span.sp-kv-value {{ $format_number(planet.population) }}
+ div
+ span.sp-kv-key Investments:
+ span.sp-kv-value {{ $format_money(planet.investmentValue) }}
+ div
+ span.sp-kv-key Corporations:
+ span.sp-kv-value {{ planet.corporationCount }}
+ div
+ span.sp-kv-key Online:
+ span.sp-kv-value {{ planet.onlineCount }}
+
+ .column.is-narrow
+ aside.menu.is-starpeace.planet-selection
+ ul.menu-list
+ li(v-for='planet in planets')
+ a(:class="{'is-active': selectedPlanetById[planet.id]}" @click.stop.prevent='togglePlanet(planet.id)')
+ span {{ planet.name }}
+
+ .column
+ .event-log.sp-scrollbar
+
+
+
+
+
+
diff --git a/components/admin/menu.vue b/components/admin/menu.vue
new file mode 100644
index 0000000..613cc65
--- /dev/null
+++ b/components/admin/menu.vue
@@ -0,0 +1,73 @@
+
+aside.menu.is-starpeace
+ template(v-for='group of optionGroups')
+ p.menu-label {{ group.label }}
+ ul.menu-list
+ li(v-for='option of group.options')
+ a(:class="{'is-active': tabId == option.id}" @click.stop.prevent='switchOption(option.id)') {{ option.label }}
+
+
+
+
+
+
diff --git a/components/admin/simulation/simulation.vue b/components/admin/simulation/simulation.vue
new file mode 100644
index 0000000..baaac6c
--- /dev/null
+++ b/components/admin/simulation/simulation.vue
@@ -0,0 +1,207 @@
+
+.card.is-starpeace
+ .card-header
+ .card-header-title Simulation
+ .card-content.sp-menu-background.columns.is-marginless.is-paddingless
+ .column.is-paddingless
+ .columns.columns.is-marginless.is-paddingless
+ .column.is-narrow
+ aside.menu.is-starpeace.planet-selection
+ ul.menu-list
+ li(v-for='planet in planets')
+ a(:class="{'is-active': planetId == planet.id}" @click.stop.prevent='togglePlanet(planet.id)')
+ span {{ planet.name }}
+ div.mt-5
+ label.checkbox.is-inline-flex.is-flex-direction-horizontal
+ input.mr-2(type='checkbox' v-model='autoRefresh' @change='setupRefreshFinances')
+ | Auto-Refresh
+
+ .column
+ table.table.is-fullwidth.is-starpeace
+ thead
+ tr
+ th Town ID
+ th Cash
+ tbody
+ tr(v-for='town in towns')
+ td {{ town.id }}
+ td {{ $format_money(town.cash) }}
+
+ .column
+ table.table.is-fullwidth.is-starpeace
+ thead
+ tr
+ th Corporation ID
+ th Cash
+ th Cashflow
+ tbody
+ tr(v-for='corporation in corporations')
+ td {{ corporation.id }}
+ td {{ $format_money(corporation.cash) }}
+ td {{ $format_money(corporation.cashflow) }}
+
+ .column
+ table.table.is-fullwidth.is-starpeace
+ thead
+ tr
+ th Company ID
+ th Cashflow
+ tbody
+ tr(v-for='company in companies')
+ td {{ company.id }}
+ td {{ $format_money(company.cashflow) }}
+
+ .column
+ table.table.is-fullwidth.is-starpeace
+ thead
+ tr
+ th Building ID
+ th Cashflow
+ tbody
+ tr(v-for='building in buildings')
+ td {{ building.id }}
+ td {{ $format_money(building.cashflow) }}
+
+
+
+
+
+
diff --git a/components/admin/tycoons/tycoons.vue b/components/admin/tycoons/tycoons.vue
new file mode 100644
index 0000000..06be4e6
--- /dev/null
+++ b/components/admin/tycoons/tycoons.vue
@@ -0,0 +1,325 @@
+
+.card.is-starpeace
+ .card-header
+ .card-header-title Tycoons
+ .card-content.sp-menu-background.columns.is-marginless.is-paddingless
+ .column.is-narrow
+ aside.menu.is-starpeace.tycoon-selection
+ ul.menu-list
+ li(v-for='tycoon in sortedTycoons')
+ a(:class="{'is-active': tycoonId == tycoon.id}" @click.stop.prevent='switchTycoon(tycoon.id)')
+ span {{ tycoon.name }}
+ span.tag.is-primary.is-starpeace.ml-3(v-if='tycoon.admin') Admin
+ span.tag.is-info.ml-3(v-else-if='tycoon.gameMaster') GM
+ span.tag.is-danger.ml-3(v-if='tycoon.bannedAt') Banned
+
+ .column.is-paddingless
+ .columns.is-marginless.is-paddingless
+ .column.is-narrow.actions-column
+ .is-flex.is-flex-direction-column(v-if='selectedTycoon')
+ button.button.is-starpeace(disabled) Change Username
+ button.button.is-starpeace.mt-1(disabled) Rename Tycoon
+ button.button.is-starpeace.mt-1(disabled) Reset Password
+ button.button.is-starpeace.mt-3(:disabled='selectedTycoon.admin || !!selectedTycoon.bannedAt' @click.stop.prevent='makeGameMaster' v-if='!selectedTycoon.gameMaster') Make GM
+ button.button.is-starpeace.mt-3(v-else @click.stop.prevent='removeGameMaster') Remove GM
+ button.button.is-starpeace.mt-1(:disabled='selectedTycoon.admin || selectedTycoon.gameMaster' @click.stop.prevent='banTycoon' v-if='!selectedTycoon.bannedAt') Ban Tycoon
+ button.button.is-starpeace.mt-1(v-else @click.stop.prevent='unbanTycoon') Unban Tycoon
+
+ .column
+ template(v-if='selectedTycoon')
+ div
+ span.sp-kv-key ID:
+ span.sp-kv-value {{ selectedTycoon.id }}
+ div
+ span.sp-kv-key Username:
+ span.sp-kv-value {{ selectedTycoon.username }}
+ div
+ span.sp-kv-key Name:
+ span.sp-kv-value {{ selectedTycoon.name }}
+ div
+ span.sp-kv-key Admin:
+ span.sp-kv-value
+ template(v-if='selectedTycoon.admin') Yes
+ template(v-else) No
+ div
+ span.sp-kv-key GM:
+ span.sp-kv-value
+ template(v-if='selectedTycoon.gameMaster') Yes
+ template(v-else) No
+ div
+ span.sp-kv-key Banned:
+ span.sp-kv-value
+ template(v-if='selectedTycoon.bannedAt')
+ span {{ selectedTycoon.bannedAt }}
+ span.ml-1 by {{ selectedTycoon.bannedBy }}
+ span.ml-1 {{ selectedTycoon.bannedReason }}
+ template(v-else)
+ span No
+
+ .column.is-narrow
+ aside.menu.is-starpeace.corporation-selection(v-if='selectedTycoon')
+ ul.menu-list(v-if='corporationIdentifiers.length')
+ li(v-for='identifier in corporationIdentifiers')
+ a(:class="{'is-active': corporationId == identifier.id}" @click.stop.prevent='switchCorporation(identifier.id)')
+ span {{ identifier.name }}
+ p.menu-label.px-3.py-2(v-else) No Corporations
+
+ .column.is-narrow
+ aside.menu.is-starpeace.company-selection(v-if='selectedTycoon')
+ ul.menu-list(v-if='companyIdentifiers.length')
+ li(v-for='identifier in companyIdentifiers')
+ a(:class="{'is-active': companyId == identifier.id}" @click.stop.prevent='switchCompany(identifier.id)')
+ misc-company-seal-icon(:seal_id='identifier.sealId')
+ span.ml-2 {{ identifier.name }}
+ p.menu-label.px-3.py-2(v-else) No Companies
+
+
+ .columns.columns.is-marginless.is-paddingless.mt-4
+ .column.is-narrow.actions-column
+ .is-flex.is-flex-direction-column(v-if='selectedCorporationIdentifier')
+ button.button.is-starpeace(disabled) Rename Corporation
+ button.button.is-starpeace.mt-1(disabled) Rename Company
+ button.button.is-starpeace.mt-3(disabled) Add Cash
+ button.button.is-starpeace.mt-1(disabled) Remove Cash
+
+ .column
+ .corporation-container.is-flex.is-flex-direction-column(v-if='selectedCorporationIdentifier')
+ span.name.p-2 {{ selectedCorporationIdentifier.name }}
+ span.p-2.is-flex.is-flex-direction-column
+ div
+ span.sp-kv-key ID:
+ span.sp-kv-value {{ selectedCorporationIdentifier.id }}
+ div
+ span.sp-kv-key Name:
+ span.sp-kv-value {{ selectedCorporationIdentifier.name }}
+ div
+ span.sp-kv-key Planet ID:
+ span.sp-kv-value {{ selectedCorporationIdentifier.planetId }}
+ template(v-if='corporation')
+ div
+ span.sp-kv-key Level ID:
+ span.sp-kv-value {{ corporation.levelId }}
+ div
+ span.sp-kv-key Prestige:
+ span.sp-kv-value {{ corporation.prestige }}
+ div
+ span.sp-kv-key Building Count:
+ span.sp-kv-value {{ corporation.buildingCount }}
+ div
+ span.sp-kv-key Cash:
+ span.sp-kv-value {{ $format_money(corporation.cash) }}
+
+ .column
+ .company-container.is-flex.is-flex-direction-column(v-if='selectedCompanyIdentifier')
+ span.name.p-2 {{ selectedCompanyIdentifier.name }}
+ span.p-2.is-flex.is-flex-direction-column
+ div
+ span.sp-kv-key ID:
+ span.sp-kv-value {{ selectedCompanyIdentifier.id }}
+ div
+ span.sp-kv-key Name:
+ span.sp-kv-value {{ selectedCompanyIdentifier.name }}
+ div
+ span.sp-kv-key Seal ID:
+ span.sp-kv-value {{ selectedCompanyIdentifier.sealId }}
+
+
+
+
+
+
diff --git a/components/toolbar/inspect/toolbar-inspect-portal.vue b/components/toolbar/inspect/toolbar-inspect-portal.vue
index 6f6fa46..e39d017 100644
--- a/components/toolbar/inspect/toolbar-inspect-portal.vue
+++ b/components/toolbar/inspect/toolbar-inspect-portal.vue
@@ -2,30 +2,44 @@
.inspect-details
.inspect-tabs.tabs.is-small.is-marginless
ul
- template(v-for='tab in tabs')
- li(:class="{ 'is-active': tab.id == tabId }" @click.stop.prevent='tabId = tab.id')
- a
- span {{ $translate(tab.label) }}
+ li(v-for='tab in tabs' :class="{ 'is-active': tabId == tab.id }" @click.stop.prevent='tabId = tab.id')
+ a {{ $translate(tab.label) }}
- .inspect-body.is-marginless
+ .inspect-body.columns.is-marginless
template(v-if="tabId == 'general'")
.column.is-paddingless.is-relative.is-clipped.p-4
span.is-italic {{ message }}
+ template(v-else-if="tabId == 'jobs'")
+ .column.is-paddingless.is-relative.is-clipped
+ toolbar-inspect-shared-tab-jobs(
+ :client-state='clientState'
+ :jobs='jobs'
+ :building='building'
+ :definition='definition'
+ :simulation='simulation'
+ )