Feature/vue settings (#4669)

This commit is contained in:
Buster "Silver Eagle" Neece 2021-10-11 04:55:25 -05:00 committed by GitHub
parent d4f2debaff
commit e610d429b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 973 additions and 614 deletions

View File

@ -147,7 +147,11 @@ return [
'files' => [
'js' => [
[
'src' => 'dist/lib/autosize/autosize.min.js',
'src' => 'dist/form.js',
'defer' => true,
],
[
'src' => 'dist/lib/autosize/autosize.min.js',
'defer' => true,
],
],

View File

@ -1,409 +0,0 @@
<?php
/**
* @var App\Environment $settings
* @var App\Version $version
*/
use App\Entity;
$releaseChannel = $version->getReleaseChannel();
$releaseChannelNames = [
App\Version::RELEASE_CHANNEL_ROLLING => __('Rolling Release'),
App\Version::RELEASE_CHANNEL_STABLE => __('Stable'),
];
$releaseChannelName = $releaseChannelNames[$releaseChannel];
return [
'tabs' => [
'system' => __('Settings'),
'security' => __('Security'),
'privacy' => __('Privacy'),
'services' => __('Services'),
],
'groups' => [
'system' => [
'use_grid' => true,
'tab' => 'system',
'elements' => [
'base_url' => [
'url',
[
'label' => __('Site Base URL'),
'description' => __(
'The base URL where this service is located. Use either the external IP address or fully-qualified domain name (if one exists) pointing to this server.'
),
'required' => true,
'form_group_class' => 'col-md-6',
],
],
'instance_name' => [
'text',
[
'label' => __('AzuraCast Instance Name'),
'description' => __(
'This name will appear as a sub-header next to the AzuraCast logo, to help identify this server.'
),
'form_group_class' => 'col-md-6',
],
],
'prefer_browser_url' => [
'toggle',
[
'label' => __('Prefer Browser URL (If Available)'),
'description' => __(
'If this setting is set to "Yes", the browser URL will be used instead of the base URL when it\'s available. Set to "No" to always use the base URL.'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => true,
'form_group_class' => 'col-md-6',
],
],
'use_radio_proxy' => [
'toggle',
[
'label' => __('Use Web Proxy for Radio'),
'description' => __(
'By default, radio stations broadcast on their own ports (i.e. 8000). If you\'re using a service like CloudFlare or accessing your radio station by SSL, you should enable this feature, which routes all radio through the web ports (80 and 443).'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => true,
'form_group_class' => 'col-md-6',
],
],
'history_keep_days' => [
'radio',
[
'label' => __('Days of Playback History to Keep'),
'description' => __(
'Set longer to preserve more playback history and listener metadata for stations. Set shorter to save disk space. '
),
'choices' => [
14 => __('Last 14 Days'),
30 => __('Last 30 Days'),
60 => __('Last 60 Days'),
365 => __('Last Year'),
730 => __('Last 2 Years'),
0 => __('Indefinitely'),
],
'default' => App\Entity\SongHistory::DEFAULT_DAYS_TO_KEEP,
'form_group_class' => 'col-sm-6',
],
],
'enable_websockets' => [
'toggle',
[
'label' => __('Use WebSockets for Now Playing Updates'),
'description' => __(
'Enables or disables the use of the newer and faster WebSocket-based system for receiving live updates on public players. You may need to disable this if you encounter problems with it.'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-6',
],
],
'enable_advanced_features' => [
'toggle',
[
'label' => __('Enable Advanced Features'),
'description' => __(
'Enable certain advanced features in the web interface, including advanced playlist configuration, station port assignment, changing base media directories and other functionality that should only be used by users who are comfortable with advanced functionality.'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-12',
],
],
],
],
'security' => [
'use_grid' => true,
'tab' => 'security',
'elements' => [
'always_use_ssl' => [
'toggle',
[
'label' => __('Always Use HTTPS'),
'description' => __(
'Set to "Yes" to always use "https://" secure URLs, and to automatically redirect to the secure URL when an insecure URL is visited.'
),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-6',
],
],
'api_access_control' => [
'text',
[
'label' => __('API "Access-Control-Allow-Origin" header'),
'class' => 'advanced',
'description' => __(
'<a href="%s" target="_blank">Learn more about this header</a>. Set to * to allow all sources, or specify a list of origins separated by a comma (,).',
'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin'
),
'default' => '',
'form_group_class' => 'col-md-12',
],
],
],
],
'privacy' => [
'tab' => 'privacy',
'elements' => [
'analytics' => [
'radio',
[
'label' => __('Listener Analytics Collection'),
'description' => __(
'Aggregate listener statistics are used to show station reports across the system. IP-based listener statistics are used to view live listener tracking and may be required for royalty reports.'
),
'choices' => [
Entity\Analytics::LEVEL_ALL => __(
'<b>Full:</b> Collect aggregate listener statistics and IP-based listener statistics'
),
Entity\Analytics::LEVEL_NO_IP => __(
'<b>Limited:</b> Only collect aggregate listener statistics'
),
Entity\Analytics::LEVEL_NONE => __('<b>None:</b> Do not collect any listener analytics'),
],
'default' => Entity\Analytics::LEVEL_ALL,
],
],
],
],
'channels' => [
'tab' => 'services',
'legend' => __('AzuraCast Update Checks'),
'elements' => [
'release_channel' => [
'markup',
[
'label' => __('Current Release Channel'),
'markup' => '<strong>' . $releaseChannelName . '</strong>',
'description' => __(
'For information on how to switch your release channel, visit <a href="%s" target="_blank">this page</a>.',
'https://docs.azuracast.com/en/getting-started/updates/release-channels'
),
],
],
'check_for_updates' => [
'toggle',
[
'label' => __('Show Update Announcements'),
'description' => __('Show new releases within your update channel on the AzuraCast homepage.'),
'default' => true,
],
],
],
],
'mail' => [
'tab' => 'services',
'legend' => __('E-mail Delivery Service'),
'description' => __('Used for "Forgot Password" functionality, web hooks and other functions.'),
'use_grid' => true,
'elements' => [
'mail_enabled' => [
'toggle',
[
'label' => __('Enable Mail Delivery'),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-12',
],
],
'mail_sender_name' => [
'text',
[
'label' => __('Sender Name'),
'default' => 'AzuraCast',
'form_group_class' => 'col-md-6',
],
],
'mail_sender_email' => [
'email',
[
'label' => __('Sender E-mail Address'),
'required' => false,
'default' => '',
'form_group_class' => 'col-md-6',
],
],
'mail_smtp_host' => [
'text',
[
'label' => __('SMTP Host'),
'default' => '',
'form_group_class' => 'col-md-4',
],
],
'mail_smtp_port' => [
'number',
[
'label' => __('SMTP Port'),
'default' => 465,
'form_group_class' => 'col-md-3',
],
],
'mail_smtp_secure' => [
'toggle',
[
'label' => __('Use Secure (TLS) SMTP Connection'),
'description' => __('Usually enabled for port 465, disabled for ports 587 or 25.'),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => true,
'form_group_class' => 'col-md-5',
],
],
'mail_smtp_username' => [
'text',
[
'label' => __('SMTP Username'),
'default' => '',
'form_group_class' => 'col-md-6',
],
],
'mail_smtp_password' => [
'password',
[
'label' => __('SMTP Password'),
'default' => '',
'form_group_class' => 'col-md-6',
],
],
],
],
'avatarServices' => [
'tab' => 'services',
'use_grid' => true,
'legend' => __('Avatar Services'),
'elements' => [
'avatar_service' => [
'radio',
[
'label' => __('Avatar Service'),
'choices' => [
App\Service\Avatar::SERVICE_LIBRAVATAR => 'Libravatar',
App\Service\Avatar::SERVICE_GRAVATAR => 'Gravatar',
App\Service\Avatar::SERVICE_DISABLED => __('Disabled'),
],
'default' => App\Service\Avatar::DEFAULT_SERVICE,
'form_group_class' => 'col-md-6',
],
],
'avatar_default_url' => [
'text',
[
'label' => __('Default Avatar URL'),
'default' => App\Service\Avatar::DEFAULT_AVATAR,
'form_group_class' => 'col-md-6',
],
],
],
],
'albumArtServices' => [
'tab' => 'services',
'use_grid' => true,
'legend' => __('Album Art Services'),
'elements' => [
'use_external_album_art_in_apis' => [
'toggle',
[
'label' => __('Check Web Services for Album Art for "Now Playing" Tracks'),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-6',
],
],
'use_external_album_art_when_processing_media' => [
'toggle',
[
'label' => __('Check Web Services for Album Art When Uploading Media'),
'selected_text' => __('Yes'),
'deselected_text' => __('No'),
'default' => false,
'form_group_class' => 'col-md-6',
],
],
'last_fm_api_key' => [
'text',
[
'label' => __('Last.fm API Key'),
'description' => __(
'<a href="%s" target="_blank">Apply for an API key here</a>. This service can provide album art for tracks where none is available locally.',
'https://www.last.fm/api/account/create'
),
'default' => '',
'form_group_class' => 'col-md-12',
],
],
],
],
'submit' => [
'legend' => '',
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => __('Save Changes'),
'class' => 'btn btn-lg btn-primary',
],
],
],
],
],
];

View File

@ -139,7 +139,7 @@ return static function (RouteCollectorProxy $app) {
->setName('admin:relays:index')
->add(new Middleware\Permissions(Acl::GLOBAL_STATIONS));
$group->map(['GET', 'POST'], '/settings', Controller\Admin\SettingsController::class)
$group->map(['GET', 'POST'], '/settings', Controller\Admin\SettingsAction::class)
->setName('admin:settings:index')
->add(new Middleware\Permissions(Acl::GLOBAL_SETTINGS));

View File

@ -1,6 +1,6 @@
$(function () {
$('form.form').each(function () {
styleForm(this);
$('form.form').not('.vue-form').each(function () {
styleForm(this);
});
});

View File

@ -101,6 +101,7 @@ $enable-print-styles: true !default;
@import 'azuracast/overrides/header';
@import 'azuracast/overrides/modal';
@import 'azuracast/overrides/progressbar';
@import 'azuracast/overrides/stepper';
@import 'azuracast/overrides/tables';
@import 'azuracast/overrides/toasts';

View File

@ -10,8 +10,20 @@ input, textarea {
div.fieldset-legend,
legend.col-form-label {
p {
margin-bottom: 0.5rem;
}
border-bottom: 2px dotted;
margin-bottom: 10px;
margin-bottom: 0.75rem;
}
.row {
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
}
.form-group {

View File

@ -0,0 +1,3 @@
.stepper-horiz {
margin-top: -1.5rem;
}

View File

@ -5,6 +5,17 @@
&::before {
border-top: $stepper-border-width solid $stepper-border-color;
}
&.done, &.active {
.stepper-icon {
background-color: $stepper-icon-bg-active;
color: color-yiq($stepper-icon-bg-active);
}
.stepper-text {
color: $stepper-text-color-active;
}
}
}
.stepper-horiz {
@ -36,19 +47,8 @@
.stepper-icon {
background-color: $stepper-icon-bg;
color: $stepper-icon-color;
.stepper.active &,
.stepper.done & {
background-color: $stepper-icon-bg-active;
color: color-yiq($stepper-icon-bg-active);
}
}
.stepper-text {
color: $stepper-text-color;
.stepper.active &,
.stepper.done & {
color: $stepper-text-color-active;
}
}

View File

@ -22,6 +22,12 @@
&:last-child::after {
display: none;
}
&.active {
.stepper-text {
font-weight: bolder;
}
}
}
.stepper-horiz {
@ -100,8 +106,4 @@
font-size: $stepper-text-font-size;
font-weight: $stepper-text-font-weight;
position: relative;
.stepper.active & {
font-weight: bolder;
}
}

View File

@ -481,18 +481,15 @@ $stepper-icon-height: $material-icon-size !default;
$stepper-inner-spacer: 0.5rem !default;
$stepper-padding-x: 1.5rem !default;
$stepper-padding-y: 1.5rem !default;
$stepper-text-color: $black-hint !default;
$stepper-text-color-active: $black-primary !default;
$stepper-text-color: $text-hint !default;
$stepper-text-color-active: $text-primary !default;
$stepper-text-font-size: 0.875rem !default;
$stepper-text-font-weight: $font-weight-regular !default;
@if $theme == 'dark' {
$stepper-border-color: $material-color-grey-800;
$stepper-icon-bg: $theme-color-3;
$stepper-icon-color: $white-primary;
$stepper-icon-height: $material-icon-size;
$stepper-text-color: $white-divider;
$stepper-text-color-active: $material-color-grey-200;
}
// Tab (Bootstrap tab)
@ -501,8 +498,8 @@ $stepper-text-font-weight: $font-weight-regular !default;
$nav-tab-bg-hover: $black-divider !default;
$nav-tab-border-color: $border-color !default;
$nav-tab-border-width: $border-width !default;
$nav-tab-color: $black-primary !default;
$nav-tab-color-active: theme-color(secondary) !default;
$nav-tab-color: $text-secondary !default;
$nav-tab-color-active: $text-primary !default;
$nav-tab-color-disabled: $black-hint !default;
$nav-tab-font-size: 0.875rem !default;
$nav-tab-font-weight: $font-weight-medium !default;
@ -518,8 +515,8 @@ $nav-tab-link-padding-y: (($nav-tab-height - $nav-tab-font-size * $nav-tab-line-
$nav-tab-bg-hover: $black-divider;
$nav-tab-border-color: $border-color;
$nav-tab-border-width: $border-width;
$nav-tab-color: $material-color-grey-200;
$nav-tab-color-active: theme-color(secondary);
$nav-tab-color: $text-secondary;
$nav-tab-color-active: $text-primary;
$nav-tab-color-disabled: $black-hint;
$nav-tab-indicator-bg: theme-color(secondary);
}

View File

@ -1,5 +1,5 @@
<template>
<form @submit.prevent="submit">
<form class="form vue-form" @submit.prevent="submit">
<section class="card mb-3" role="region">
<div class="card-header bg-primary-dark">
<h2 class="card-title">

View File

@ -0,0 +1,188 @@
<template>
<form class="form vue-form" @submit.prevent="submit">
<slot name="preCard"></slot>
<b-card no-body>
<div class="card-header bg-primary-dark">
<h2 class="card-title">
<slot name="cardTitle">
<translate key="lang_settings">System Settings</translate>
</slot>
</h2>
</div>
<slot name="cardUpper"></slot>
<b-alert variant="danger" :show="error != null">{{ error }}</b-alert>
<b-overlay variant="card" :show="loading">
<b-tabs card lazy justified>
<settings-general-tab :form="$v.form"
:tab-class="getTabClass($v.generalTab)"></settings-general-tab>
<settings-security-privacy-tab :form="$v.form"
:tab-class="getTabClass($v.securityPrivacyTab)"></settings-security-privacy-tab>
<settings-services-tab :form="$v.form" :tab-class="getTabClass($v.servicesTab)"
:release-channel="releaseChannel"></settings-services-tab>
</b-tabs>
</b-overlay>
<b-card-body body-class="card-padding-sm">
<b-button size="lg" type="submit" variant="primary" :disabled="$v.form.$invalid">
<slot name="submitButtonName">
<translate key="lang_btn_save_changes">Save Changes</translate>
</slot>
</b-button>
</b-card-body>
</b-card>
</form>
</template>
<script>
import SettingsGeneralTab from "./Settings/GeneralTab";
import SettingsServicesTab from "./Settings/ServicesTab";
import {validationMixin} from "vuelidate";
import {required} from 'vuelidate/dist/validators.min.js';
import SettingsSecurityPrivacyTab from "~/components/Admin/Settings/SecurityPrivacyTab";
export default {
name: 'AdminSettings',
components: {SettingsSecurityPrivacyTab, SettingsServicesTab, SettingsGeneralTab},
emits: ['saved'],
props: {
apiUrl: String,
releaseChannel: {
type: String,
default: 'rolling',
required: false
}
},
mixins: [
validationMixin
],
data() {
return {
loading: true,
error: null,
form: {},
};
},
validations: {
form: {
base_url: {required},
instance_name: {},
prefer_browser_url: {},
use_radio_proxy: {},
history_keep_days: {required},
enable_websockets: {},
enable_advanced_features: {},
analytics: {required},
always_use_ssl: {},
api_access_control: {},
check_for_updates: {},
mail_enabled: {},
mail_sender_name: {},
mail_sender_email: {},
mail_smtp_host: {},
mail_smtp_port: {},
mail_smtp_secure: {},
mail_smtp_username: {},
mail_smtp_password: {},
avatar_service: {},
avatar_default_url: {},
use_external_album_art_in_apis: {},
use_external_album_art_when_processing_media: {},
last_fm_api_key: {}
},
generalTab: [
'form.base_url', 'form.instance_name', 'form.prefer_browser_url', 'form.use_radio_proxy',
'form.history_keep_days', 'form.enable_websockets', 'form.enable_advanced_features'
],
securityPrivacyTab: [
'form.analytics', 'form.always_use_ssl', 'form.api_access_control'
],
servicesTab: [
'form.check_for_updates', 'form.mail_enabled', 'form.mail_sender_name', 'form.mail_sender_email',
'form.mail_smtp_host', 'form.mail_smtp_port', 'form.mail_smtp_secure', 'form.mail_smtp_username',
'form.mail_smtp_password', 'form.avatar_service', 'form.avatar_default_url',
'form.use_external_album_art_in_apis', 'form.use_external_album_art_when_processing_media',
'form.last_fm_api_key',
]
},
mounted() {
this.relist();
},
methods: {
getTabClass(validationGroup) {
if (!this.loading && validationGroup.$invalid) {
return 'text-danger';
}
return null;
},
relist() {
this.$v.form.$reset();
this.loading = true;
this.axios.get(this.apiUrl).then((resp) => {
this.populateForm(resp.data);
this.loading = false;
}).catch((error) => {
this.close();
});
},
populateForm(data) {
this.form = {
base_url: data.base_url,
instance_name: data.instance_name,
prefer_browser_url: data.prefer_browser_url,
use_radio_proxy: data.use_radio_proxy,
history_keep_days: data.history_keep_days,
enable_websockets: data.enable_websockets,
enable_advanced_features: data.enable_advanced_features,
analytics: data.analytics,
always_use_ssl: data.always_use_ssl,
api_access_control: data.api_access_control,
check_for_updates: data.check_for_updates,
mail_enabled: data.mail_enabled,
mail_sender_name: data.mail_sender_name,
mail_sender_email: data.mail_sender_email,
mail_smtp_host: data.mail_smtp_host,
mail_smtp_port: data.mail_smtp_port,
mail_smtp_secure: data.mail_smtp_secure,
mail_smtp_username: data.mail_smtp_username,
mail_smtp_password: data.mail_smtp_password,
avatar_service: data.avatar_service,
avatar_default_url: data.avatar_default_url,
use_external_album_art_in_apis: data.use_external_album_art_in_apis,
use_external_album_art_when_processing_media: data.use_external_album_art_when_processing_media,
last_fm_api_key: data.last_fm_api_key
}
},
submit() {
this.$v.form.$touch();
if (this.$v.form.$anyError) {
return;
}
this.$wrapWithLoading(
this.axios({
method: 'PUT',
url: this.apiUrl,
data: this.form
})
).then((resp) => {
this.$emit('saved');
this.$notifySuccess(this.$gettext('Changes saved.'));
this.relist();
});
}
}
}
</script>

View File

@ -0,0 +1,140 @@
<template>
<b-tab :title="langTabTitle" :title-link-class="tabClass">
<b-form-fieldset>
<b-row>
<b-wrapped-form-group class="col-md-6" id="edit_form_base_url" :field="form.base_url" input-type="url">
<template #label>
<translate key="lang_edit_form_base_url">Site Base URL</translate>
</template>
<template #description>
<translate key="lang_edit_form_base_url_desc">The base URL where this service is located. Use either the external IP address or fully-qualified domain name (if one exists) pointing to this server.</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_instance_name" :field="form.instance_name">
<template #label>
<translate key="lang_edit_form_instance_name">AzuraCast Instance Name</translate>
</template>
<template #description>
<translate key="lang_edit_form_instance_name_desc">This name will appear as a sub-header next to the AzuraCast logo, to help identify this server.</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_prefer_browser_url"
:field="form.prefer_browser_url">
<template #description>
<translate key="lang_edit_form_prefer_browser_url_desc">If this setting is set to "Yes", the browser URL will be used instead of the base URL when it's available. Set to "No" to always use the base URL.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_prefer_browser_url">Prefer Browser URL (If Available)</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_use_radio_proxy"
:field="form.use_radio_proxy">
<template #description>
<translate key="lang_edit_form_use_radio_proxy_desc">By default, radio stations broadcast on their own ports (i.e. 8000). If you're using a service like CloudFlare or accessing your radio station by SSL, you should enable this feature, which routes all radio through the web ports (80 and 443).</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_use_radio_proxy">Use Web Proxy for Radio</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_history_keep_days" :field="form.history_keep_days">
<template #label>
<translate key="lang_edit_form_history_keep_days">Days of Playback History to Keep</translate>
</template>
<template #description>
<translate key="lang_edit_form_history_keep_days_desc">Set longer to preserve more playback history and listener metadata for stations. Set shorter to save disk space.</translate>
</template>
<template #default="props">
<b-form-radio-group stacked :id="props.id" v-model="props.field.$model"
:options="historyKeepDaysOptions"></b-form-radio-group>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_enable_websockets"
:field="form.enable_websockets">
<template #description>
<translate key="lang_edit_form_enable_websockets_desc">Enables or disables the use of the newer and faster WebSocket-based system for receiving live updates on public players. You may need to disable this if you encounter problems with it.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_enable_websockets">Use WebSockets for Now Playing Updates</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_enable_advanced_features"
:field="form.enable_websockets">
<template #description>
<translate key="lang_edit_form_enable_advanced_features_desc">Enable certain advanced features in the web interface, including advanced playlist configuration, station port assignment, changing base media directories and other functionality that should only be used by users who are comfortable with advanced functionality.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_enable_advanced_features">Enable Advanced Features</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
</b-tab>
</template>
<script>
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import BFormFieldset from "~/components/Form/BFormFieldset";
export default {
name: 'SettingsGeneralTab',
components: {BFormFieldset, BWrappedFormGroup},
props: {
form: Object,
tabClass: {},
},
computed: {
langTabTitle() {
return this.$gettext('Settings');
},
historyKeepDaysOptions() {
return [
{
value: 14,
text: this.$gettext('Last 14 Days')
},
{
value: 30,
text: this.$gettext('Last 30 Days')
},
{
value: 60,
text: this.$gettext('Last 60 Days')
},
{
value: 365,
text: this.$gettext('Last Year')
},
{
value: 730,
text: this.$gettext('Last 2 Years')
},
{
value: 0,
text: this.$gettext('Indefinitely')
},
]
},
}
}
</script>

View File

@ -0,0 +1,99 @@
<template>
<b-tab :title="langTabTitle" :title-link-class="tabClass">
<b-form-fieldset>
<template #label>
<translate key="lang_privacy_hdr">Privacy</translate>
</template>
<b-row>
<b-wrapped-form-group class="col-md-12" id="edit_form_analytics" :field="form.analytics">
<template #label>
<translate key="lang_edit_form_analytics">Listener Analytics Collection</translate>
</template>
<template #description>
<translate key="lang_edit_form_analytics_desc">Aggregate listener statistics are used to show station reports across the system. IP-based listener statistics are used to view live listener tracking and may be required for royalty reports.</translate>
</template>
<template #default="props">
<b-form-radio-group stacked :id="props.id" v-model="props.field.$model">
<b-form-radio value="all">
<b>
<translate key="analytics_opt_full_title">Full:</translate>
</b>
<translate key="analytics_opt_full_desc">Collect aggregate listener statistics and IP-based listener statistics</translate>
</b-form-radio>
<b-form-radio value="no_ip">
<b>
<translate key="analytics_opt_no_ip_title">Limited:</translate>
</b>
<translate
key="analytics_opt_no_ip_desc">Only collect aggregate listener statistics</translate>
</b-form-radio>
<b-form-radio value="none">
<b>
<translate key="analytics_opt_none_title">None:</translate>
</b>
<translate
key="analytics_opt_none_desc">Do not collect any listener analytics</translate>
</b-form-radio>
</b-form-radio-group>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
<b-form-fieldset>
<template #label>
<translate key="lang_security_hdr">Security</translate>
</template>
<b-row>
<b-wrapped-form-group class="col-md-12" id="edit_form_always_use_ssl"
:field="form.always_use_ssl">
<template #description>
<translate key="lang_edit_form_always_use_ssl_desc">Set to "Yes" to always use "https://" secure URLs, and to automatically redirect to the secure URL when an insecure URL is visited.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate key="lang_edit_form_always_use_ssl">Always Use HTTPS</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12" id="edit_form_api_access_control"
:field="form.api_access_control">
<template #label>
<translate
key="lang_edit_form_api_access_control">API "Access-Control-Allow-Origin" Header</translate>
</template>
<template #description>
<translate key="lang_edit_form_api_access_control_desc">Set to * to allow all sources, or specify a list of origins separated by a comma (,).</translate>
<br>
<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"
target="_blank">
<translate key="lang_edit_form_api_header_desc">Learn more about this header.</translate>
</a>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
</b-tab>
</template>
<script>
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import BFormFieldset from "~/components/Form/BFormFieldset";
export default {
name: 'SettingsSecurityPrivacyTab',
components: {BFormFieldset, BWrappedFormGroup},
props: {
form: Object,
tabClass: {},
},
computed: {
langTabTitle() {
return this.$gettext('Security & Privacy');
},
}
}
</script>

View File

@ -0,0 +1,224 @@
<template>
<b-tab :title="langTabTitle" :title-link-class="tabClass">
<b-form-fieldset>
<template #label>
<translate key="lang_section_update_checks">AzuraCast Update Checks</translate>
</template>
<b-row>
<b-form-markup class="col-md-6" id="form_release_channel">
<template #label>
<translate key="lang_release_channel">Current Release Channel</translate>
</template>
<template #description>
<a href="https://docs.azuracast.com/en/getting-started/updates/release-channels"
target="_blank">
<translate key="lang_switch_release_channel">Learn more about release channels in the AzuraCast docs.</translate>
</a>
</template>
<p class="card-text font-weight-bold">
{{ langReleaseChannel }}
</p>
</b-form-markup>
<b-wrapped-form-group class="col-md-6" id="edit_form_check_for_updates"
:field="form.check_for_updates">
<template #description>
<translate key="lang_edit_form_check_for_updates_desc">Show new releases within your update channel on the AzuraCast homepage.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate key="lang_edit_form_check_for_updates">Show Update Announcements</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
<b-form-fieldset>
<template #label>
<translate key="lang_section_email_delivery">E-mail Delivery Service</translate>
</template>
<template #description>
<translate key="lang_section_email_delivery_desc">Used for "Forgot Password" functionality, web hooks and other functions.</translate>
</template>
<b-row>
<b-wrapped-form-group class="col-md-12" id="edit_form_mail_enabled"
:field="form.mail_enabled">
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate key="lang_edit_form_mail_enabled">Enable Mail Delivery</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
</b-row>
<b-row v-if="form.mail_enabled.$model">
<b-wrapped-form-group class="col-md-6" id="edit_form_mail_sender_name"
:field="form.mail_sender_name">
<template #label>
<translate key="lang_edit_form_mail_sender_name">Sender Name</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_mail_sender_email"
:field="form.mail_sender_email" input-type="email">
<template #label>
<translate key="lang_edit_form_mail_sender_email">Sender E-mail Address</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-4" id="edit_form_mail_smtp_host"
:field="form.mail_smtp_host">
<template #label>
<translate key="lang_edit_form_mail_smtp_host">SMTP Host</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-3" id="edit_form_mail_smtp_port"
:field="form.mail_smtp_port" input-type="number">
<template #label>
<translate key="lang_edit_form_mail_smtp_port">SMTP Port</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-5" id="edit_form_mail_smtp_secure"
:field="form.mail_smtp_secure">
<template #description>
<translate key="lang_edit_form_mail_smtp_secure_desc">Usually enabled for port 465, disabled for ports 587 or 25.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_mail_smtp_secure">Use Secure (TLS) SMTP Connection</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_mail_smtp_username"
:field="form.mail_smtp_username">
<template #label>
<translate key="lang_edit_form_mail_smtp_username">SMTP Username</translate>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_mail_smtp_password"
:field="form.mail_smtp_password" input-type="password">
<template #label>
<translate key="lang_edit_form_mail_smtp_password">SMTP Password</translate>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
<b-form-fieldset>
<template #label>
<translate key="lang_section_avatar_services">Avatar Services</translate>
</template>
<b-row>
<b-wrapped-form-group class="col-md-6" id="edit_form_avatar_service" :field="form.avatar_service">
<template #label>
<translate key="lang_edit_form_avatar_service">Avatar Service</translate>
</template>
<template #default="props">
<b-form-radio-group stacked :id="props.id" v-model="props.field.$model"
:options="avatarServiceOptions"></b-form-radio-group>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="edit_form_avatar_default_url"
:field="form.avatar_default_url">
<template #label>
<translate key="lang_edit_form_avatar_default_url">Default Avatar URL</translate>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
<b-form-fieldset>
<template #label>
<translate key="lang_section_album_art_services">Album Art Services</translate>
</template>
<b-row>
<b-wrapped-form-group class="col-md-6" id="use_external_album_art_in_apis"
:field="form.use_external_album_art_in_apis">
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_use_external_album_art_in_apis">Check Web Services for Album Art for "Now Playing" Tracks</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-6" id="use_external_album_art_when_processing_media"
:field="form.use_external_album_art_when_processing_media">
<template #default="props">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate
key="lang_edit_form_use_external_album_art_when_processing_media">Check Web Services for Album Art When Uploading Media</translate>
</b-form-checkbox>
</template>
</b-wrapped-form-group>
<b-wrapped-form-group class="col-md-12" id="edit_form_last_fm_api_key" :field="form.last_fm_api_key">
<template #label>
<translate key="lang_edit_form_last_fm_api_key">Last.fm API Key</translate>
</template>
<template #description>
<translate key="lang_edit_form_last_fm_api_key_desc">This service can provide album art for tracks where none is available locally.</translate>
<br>
<a href="https://www.last.fm/api/account/create" target="_blank">
<translate key="lang_edit_form_last_fm_url">Apply for an API key at Last.fm</translate>
</a>
</template>
</b-wrapped-form-group>
</b-row>
</b-form-fieldset>
</b-tab>
</template>
<script>
import BFormMarkup from "~/components/Form/BFormMarkup";
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup";
import BFormFieldset from "~/components/Form/BFormFieldset";
export default {
name: 'SettingsServicesTab',
components: {BFormFieldset, BWrappedFormGroup, BFormMarkup},
props: {
form: Object,
tabClass: {},
releaseChannel: String,
},
computed: {
langTabTitle() {
return this.$gettext('Services');
},
langReleaseChannel() {
return (this.releaseChannel === 'stable')
? this.$gettext('Stable')
: this.$gettext('Rolling Release');
},
avatarServiceOptions() {
return [
{
value: 'libravatar',
text: 'Libravatar'
},
{
value: 'gravatar',
text: 'Gravatar'
},
{
value: 'disabled',
text: this.$gettext('Disabled')
}
]
},
}
}
</script>

View File

@ -4,7 +4,7 @@
<b-overlay variant="card" :show="loading">
<b-alert variant="danger" :show="error != null">{{ error }}</b-alert>
<b-form class="form" @submit.prevent="doSubmit">
<b-form class="form vue-form" @submit.prevent="doSubmit">
<slot name="default" v-bind="slotProps">
</slot>

View File

@ -0,0 +1,47 @@
<script>
export default {
name: 'BFormFieldset',
methods: {
getSlot(name, scope = {}) {
let slot = this.$scopedSlots[name] || this.$slots[name]
return typeof slot === 'function' ? slot(scope) : slot
}
},
render(h) {
const legendSlot = this.getSlot('label');
const descriptionSlot = this.getSlot('description');
let header = h();
if (legendSlot) {
const description = descriptionSlot
? h('p', {}, [descriptionSlot])
: h();
const legend = h('legend', {}, [legendSlot]);
header = h(
'div',
{
staticClass: 'fieldset-legend'
},
[
legend,
description
]
);
}
return h(
'fieldset',
{
staticClass: 'form-group'
},
[
header,
this.getSlot('default') || h()
]
)
}
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<b-form-group v-bind="$attrs" :label-for="id">
<template #default="slotProps">
<div :id="id">
<slot name="default" v-bind="slotProps"></slot>
</div>
</template>
<template #label="slotProps">
<slot name="label" v-bind="slotProps"></slot>
</template>
<template #description="slotProps">
<slot name="description" v-bind="slotProps"></slot>
</template>
<slot v-for="(_, name) in $slots" :name="name" :slot="name"/>
<template v-for="(_, name) in filteredScopedSlots" :slot="name" slot-scope="slotData">
<slot :name="name" v-bind="slotData"/>
</template>
</b-form-group>
</template>
<script>
import _ from "lodash";
export default {
name: 'BFormMarkup',
props: {
id: {
type: String,
required: true
},
},
computed: {
filteredScopedSlots() {
return _.filter(this.$scopedSlots, (slot, name) => {
return !_.includes([
'default', 'label', 'description'
], name);
});
},
labelClassWithRequired() {
let labelClass = this.labelClass;
if (this.isRequired) {
labelClass += ' required';
}
return labelClass;
},
isRequired() {
return _.has(this.field, 'required');
}
}
}
</script>

View File

@ -0,0 +1,45 @@
<template>
<admin-settings :api-url="apiUrl" :release-channel="releaseChannel" @saved="onSaved">
<template #preCard>
<setup-step step="3"></setup-step>
</template>
<template #cardTitle>
<translate key="lang_setup_settings_hdr">Customize AzuraCast Settings</translate>
</template>
<template #cardUpper>
<info-card>
<translate key="lang_setup_settings_info">Complete the setup process by providing some information about your broadcast environment. These settings can be changed later from the administration panel.</translate>
</info-card>
</template>
<template #submitButtonName>
<translate key="lang_setup_settings_btn">Save and Continue</translate>
</template>
</admin-settings>
</template>
<script>
import AdminSettings from "~/components/Admin/Settings";
import SetupStep from "./SetupStep";
import InfoCard from "~/components/Common/InfoCard";
export default {
name: 'SetupSettings',
components: {InfoCard, SetupStep, AdminSettings},
props: {
apiUrl: String,
releaseChannel: {
type: String,
default: 'rolling',
required: false
},
continueUrl: {
type: String,
required: true
}
},
methods: {
onSaved() {
window.location.href = this.continueUrl;
}
}
}
</script>

View File

@ -0,0 +1,42 @@
<template>
<div class="stepper-horiz">
<div class="stepper done">
<div class="stepper-icon">
<icon v-if="step > 1" icon="check"></icon>
<span v-else>1</span>
</div>
<span class="stepper-text">
<translate key="lang_step_register">Create Account</translate>
</span>
</div>
<div class="stepper done">
<div class="stepper-icon">
<icon v-if="step > 2" icon="check"></icon>
<span v-else>2</span>
</div>
<span class="stepper-text">
<translate key="lang_step_station">Create Station</translate>
</span>
</div>
<div class="stepper active">
<div class="stepper-icon">
<span>3</span>
</div>
<span class="stepper-text">
<translate key="lang_step_settings">System Settings</translate>
</span>
</div>
</div>
</template>
<script>
import Icon from "~/components/Common/Icon";
export default {
name: 'SetupStep',
components: {Icon},
props: {
step: Number
}
}
</script>

View File

@ -7,7 +7,7 @@
<translate key="lang_edit_form_enable_autodj_desc">If enabled, the AutoDJ on this installation will automatically play music to this mount point.</translate>
</template>
<template #default="props">
<b-form-checkbox :id="props.id" v-model="form.enable_autodj.$model">
<b-form-checkbox :id="props.id" v-model="props.field.$model">
<translate key="lang_edit_form_enable_autodj">Broadcast AutoDJ to Remote Station</translate>
</b-form-checkbox>
</template>

View File

@ -0,0 +1,7 @@
import initBase from '~/base.js';
import '~/vendor/bootstrapVue.js';
import AdminSettings from '~/components/Admin/Settings.vue';
export default initBase(AdminSettings);

View File

@ -0,0 +1,7 @@
import initBase from '~/base.js';
import '~/vendor/bootstrapVue.js';
import SetupSettings from '~/components/Setup/Settings.vue';
export default initBase(SetupSettings);

View File

@ -11,6 +11,7 @@ module.exports = {
AdminBranding: '~/pages/Admin/Branding.js',
AdminCustomFields: '~/pages/Admin/CustomFields.js',
AdminPermissions: '~/pages/Admin/Permissions.js',
AdminSettings: '~/pages/Admin/Settings.js',
AdminStorageLocations: '~/pages/Admin/StorageLocations.js',
PublicFullPlayer: '~/pages/Public/FullPlayer.js',
PublicHistory: '~/pages/Public/History.js',
@ -19,6 +20,7 @@ module.exports = {
PublicRequests: '~/pages/Public/Requests.js',
PublicSchedule: '~/pages/Public/Schedule.js',
PublicWebDJ: '~/pages/Public/WebDJ.js',
SetupSettings: '~/pages/Setup/Settings.js',
StationsMedia: '~/pages/Stations/Media.js',
StationsMounts: '~/pages/Stations/Mounts.js',
StationsPlaylists: '~/pages/Stations/Playlists.js',

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Version;
use Psr\Http\Message\ResponseInterface;
class SettingsAction
{
public function __invoke(
ServerRequest $request,
Response $response,
Version $version
): ResponseInterface {
$router = $request->getRouter();
return $request->getView()->renderVuePage(
response: $response,
component: 'Vue_AdminSettings',
id: 'admin-settings',
title: __('System Settings'),
props: [
'apiUrl' => (string)$router->named('api:admin:settings'),
'releaseChannel' => $version->getReleaseChannel(),
],
);
}
}

View File

@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Admin;
use App\Form\SettingsForm;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Session\Flash;
use DI\FactoryInterface;
use Psr\Http\Message\ResponseInterface;
class SettingsController
{
public function __invoke(
ServerRequest $request,
Response $response,
FactoryInterface $factory
): ResponseInterface {
$form = $factory->make(SettingsForm::class);
if (false !== $form->process($request)) {
$request->getFlash()->addMessage(__('Changes saved.'), Flash::SUCCESS);
return $response->withRedirect($request->getUri()->getPath());
}
return $request->getView()->renderToResponse(
$response,
'system/form_page',
[
'form' => $form,
'render_mode' => 'edit',
'title' => __('System Settings'),
]
);
}
}

View File

@ -7,11 +7,11 @@ namespace App\Controller\Frontend;
use App\Entity;
use App\Environment;
use App\Exception\NotLoggedInException;
use App\Form\SettingsForm;
use App\Form\StationForm;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Session\Flash;
use App\Version;
use DI\FactoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
@ -183,40 +183,26 @@ class SetupController
public function settingsAction(
ServerRequest $request,
Response $response,
FactoryInterface $factory
Version $version
): ResponseInterface {
$settingsForm = $factory->make(SettingsForm::class);
$router = $request->getRouter();
// Verify current step.
$current_step = $this->getSetupStep($request);
if ($current_step !== 'settings' && $this->environment->isProduction()) {
return $response->withRedirect((string)$request->getRouter()->named('setup:' . $current_step));
return $response->withRedirect((string)$router->named('setup:' . $current_step));
}
if ($settingsForm->process($request)) {
$settings = $this->settingsRepo->readSettings();
$settings->updateSetupComplete();
$this->settingsRepo->writeSettings($settings);
// Notify the user and redirect to homepage.
$request->getFlash()->addMessage(
sprintf(
'<b>%s</b><br>%s',
__('Setup is now complete!'),
__('Continue setting up your station in the main AzuraCast app.')
),
Flash::SUCCESS
);
return $response->withRedirect((string)$request->getRouter()->named('dashboard'));
}
return $request->getView()->renderToResponse(
$response,
'frontend/setup/settings',
[
'form' => $settingsForm,
]
return $request->getView()->renderVuePage(
response: $response,
component: 'Vue_SetupSettings',
id: 'setup-settings',
title: __('System Settings'),
props: [
'apiUrl' => (string)$router->named('api:admin:settings'),
'releaseChannel' => $version->getReleaseChannel(),
'continueUrl' => (string)$router->named('dashboard'),
],
);
}
}

View File

@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Form;
use App\Config;
use App\Entity;
use App\Environment;
use App\Http\ServerRequest;
use App\Version;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class SettingsForm extends AbstractSettingsForm
{
public function __construct(
EntityManagerInterface $em,
Serializer $serializer,
ValidatorInterface $validator,
Entity\Repository\SettingsRepository $settingsRepo,
Environment $environment,
Version $version,
Config $config
) {
$formConfig = $config->get(
'forms/settings',
[
'settings' => $environment,
'version' => $version,
]
);
parent::__construct($settingsRepo, $environment, $em, $serializer, $validator, $formConfig);
}
/** @inheritDoc */
public function process(ServerRequest $request, $record = null): object|bool
{
if ('https' !== $request->getUri()->getScheme()) {
$alwaysUseSsl = $this->getField('always_use_ssl');
$alwaysUseSsl->setAttribute('disabled', 'disabled');
$alwaysUseSsl->setOption(
'description',
__('Visit this page from a secure connection to enforce secure URLs on all pages.')
);
}
return parent::process($request, $record);
}
}

View File

@ -1,47 +0,0 @@
<?php
/** @var \App\Assets $assets */
$assets
->addInlineJs($this->fetch('partials/station_form.js'), 99);
$this->start('stepper');
?>
<div class="stepper-horiz">
<div class="stepper done">
<div class="stepper-icon">
<i class="material-icons" aria-hidden="true">check</i>
</div>
<span class="stepper-text"><?=__('Create Account') ?></span>
</div>
<div class="stepper done">
<div class="stepper-icon">
<i class="material-icons" aria-hidden="true">check</i>
</div>
<span class="stepper-text"><?=__('Create Station') ?></span>
</div>
<div class="stepper active">
<div class="stepper-icon">
<span>3</span>
</div>
<span class="stepper-text"><?=__('System Settings') ?></span>
</div>
</div>
<?php
$this->stop();
$this->start('prefix');
?>
<div class="card-body">
<p>
<?=__('Complete the setup process by providing some information about your broadcast environment. These settings can be changed later from the administration panel.') ?>
</p>
</div>
<?php
$this->stop();
echo $this->fetch('system/form_page', [
'title' => __('Customize AzuraCast Settings'),
'form' => $form,
'prefix' => $this->section('prefix'),
'stepper' => $this->section('stepper'),
]);
?>

View File

@ -72,7 +72,7 @@ class Admin_RecordsCest extends CestAbstract
$I->wantTo('Manage settings.');
$I->amOnPage('/admin/settings');
$I->submitForm('.form', []);
$I->seeCurrentUrlEquals('/admin/settings');
$I->seeResponseCodeIs(200);
$I->seeInTitle('System Settings');
}
}

View File

@ -62,6 +62,13 @@ abstract class CestAbstract
/* Walk through the steps of completing setup automatically. */
$this->setupCompleteUser($I);
$this->setupCompleteStations($I);
$this->setupCompleteSettings($I);
}
protected function setupCompleteUser(FunctionalTester $I): void
{
// Create administrator account.
$role = new Entity\Role;
$role->setName('Super Administrator');
@ -83,7 +90,10 @@ abstract class CestAbstract
$this->em->flush();
$this->di->get(App\Acl::class)->reload();
}
protected function setupCompleteStations(FunctionalTester $I): void
{
$test_station = new Entity\Station();
$test_station->setName('Functional Test Radio');
$test_station->setDescription('Test radio station.');
@ -91,7 +101,10 @@ abstract class CestAbstract
$test_station->setBackendType(App\Radio\Adapters::DEFAULT_BACKEND);
$this->test_station = $this->stationRepo->create($test_station);
}
protected function setupCompleteSettings(FunctionalTester $I): void
{
// Set settings.
$settings = $this->settingsRepo->readSettings();
$settings->updateSetupComplete();

View File

@ -54,12 +54,12 @@ class Frontend_SetupCest extends CestAbstract
protected function setupSettings(FunctionalTester $I): void
{
$I->submitForm('.form', [
'base_url' => 'http://localhost',
]);
$I->seeResponseCodeIs(200);
$I->seeCurrentUrlEquals('/dashboard');
$I->seeInSource('Setup is now complete!');
$I->seeInTitle('System Settings');
$this->setupCompleteSettings($I);
$I->amOnPage('/dashboard');
$I->seeResponseCodeIs(200);
}
}