diff --git a/lib/api/api.js b/lib/api/api.js
index 66c41b7..da65b16 100644
--- a/lib/api/api.js
+++ b/lib/api/api.js
@@ -16,6 +16,7 @@ import { demoRouter } from './routes/demoRouter.js';
import logger from '../services/logger.js';
import { listingsRouter } from './routes/listingsRouter.js';
import { getSettings } from '../services/storage/settingsStorage.js';
+import { featureRouter } from './routes/featureRouter.js';
const service = restana();
const staticService = files(path.join(getDirName(), '../ui/public'));
const PORT = (await getSettings()).port || 9998;
@@ -39,6 +40,7 @@ service.use('/api/version', versionRouter);
service.use('/api/jobs', jobRouter);
service.use('/api/login', loginRouter);
service.use('/api/listings', listingsRouter);
+service.use('/api/features', featureRouter);
//this route is unsecured intentionally as it is being queried from the login page
service.use('/api/demo', demoRouter);
diff --git a/lib/api/routes/featureRouter.js b/lib/api/routes/featureRouter.js
new file mode 100644
index 0000000..2dbeba2
--- /dev/null
+++ b/lib/api/routes/featureRouter.js
@@ -0,0 +1,12 @@
+import restana from 'restana';
+import getFeatures from '../../features.js';
+const service = restana();
+const featureRouter = service.newRouter();
+
+featureRouter.get('/', async (req, res) => {
+ const features = getFeatures();
+ res.body = Object.assign({}, { features });
+ res.send();
+});
+
+export { featureRouter };
diff --git a/lib/features.js b/lib/features.js
new file mode 100644
index 0000000..fa3785e
--- /dev/null
+++ b/lib/features.js
@@ -0,0 +1,9 @@
+const FEATURES = {
+ WATCHLIST_MANAGEMENT: false,
+};
+
+export default function getFeatures() {
+ return {
+ ...FEATURES,
+ };
+}
diff --git a/ui/src/App.jsx b/ui/src/App.jsx
index 317e1f7..423433d 100644
--- a/ui/src/App.jsx
+++ b/ui/src/App.jsx
@@ -21,7 +21,7 @@ import Navigation from './components/navigation/Navigation.jsx';
import { Layout } from '@douyinfe/semi-ui';
import FredyFooter from './components/footer/FredyFooter.jsx';
import ProcessingTimes from './views/jobs/ProcessingTimes.jsx';
-import ListingManagement from './views/listings/management/ListingManagement.jsx';
+import WatchlistManagement from './views/listings/management/WatchlistManagement.jsx';
export default function FredyApp() {
const actions = useActions();
@@ -35,6 +35,7 @@ export default function FredyApp() {
async function init() {
await actions.user.getCurrentUser();
if (!needsLogin()) {
+ await actions.features.getFeatures();
await actions.provider.getProvider();
await actions.jobs.getJobs();
await actions.jobs.getProcessingTimes();
@@ -92,7 +93,7 @@ export default function FredyApp() {
} />
} />
} />
- } />
+ } />
{/* Permission-aware routes */}
},
@@ -21,15 +23,19 @@ export default function Navigation({ isAdmin }) {
];
if (isAdmin) {
+ const settingsItems = [
+ { itemKey: '/users', text: 'User Management' },
+ { itemKey: '/generalSettings', text: 'General Settings' },
+ ];
+ if (watchlistFeature) {
+ settingsItems.push({ itemKey: '/watchlistManagement', text: 'Watchlist Management' });
+ }
+
items.push({
itemKey: 'settings',
text: 'Settings',
icon: ,
- items: [
- { itemKey: '/users', text: 'User Management' },
- { itemKey: '/listingManagement', text: 'Listing Management' },
- { itemKey: '/generalSettings', text: 'General Settings' },
- ],
+ items: settingsItems,
});
}
diff --git a/ui/src/components/table/listings/ListingsTable.jsx b/ui/src/components/table/listings/ListingsTable.jsx
index f00db25..f29f6cc 100644
--- a/ui/src/components/table/listings/ListingsTable.jsx
+++ b/ui/src/components/table/listings/ListingsTable.jsx
@@ -24,6 +24,7 @@ import { format } from '../../../services/time/timeService.js';
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
import { xhrDelete, xhrPost } from '../../../services/xhr.js';
import { useNavigate } from 'react-router-dom';
+import { useFeature } from '../../../hooks/featureHook.js';
const getColumns = (provider, setProviderFilter, jobs, setJobNameFilter) => {
return [
@@ -239,6 +240,7 @@ export default function ListingsTable() {
const jobs = useSelector((state) => state.jobs.jobs);
const navigate = useNavigate();
+ const watchlistFeature = useFeature('WATCHLIST_MANAGEMENT') || false;
const actions = useActions();
const [page, setPage] = useState(1);
const pageSize = 10;
@@ -350,14 +352,16 @@ export default function ListingsTable() {
placeholder="Search"
onChange={handleFilterChange}
/>
-
+ {watchlistFeature && (
+
+ )}
state.features);
+ if (Object.keys(currentFeatureFlags || {}).length === 0) {
+ return null;
+ }
+
+ if (currentFeatureFlags[name] == null) {
+ console.warn(`Feature flag with name ${name} is unknown.`);
+ return null;
+ }
+
+ return currentFeatureFlags[name];
+}
diff --git a/ui/src/services/state/store.js b/ui/src/services/state/store.js
index 9ee44a4..3d2fd81 100644
--- a/ui/src/services/state/store.js
+++ b/ui/src/services/state/store.js
@@ -48,6 +48,16 @@ export const useFredyState = create(
}
},
},
+ features: {
+ async getFeatures() {
+ try {
+ const response = await xhrGet('/api/features');
+ set((state) => ({ ...state.features, ...response.json }));
+ } catch (Exception) {
+ console.error('Error while trying to get resource for api/features. Error:', Exception);
+ }
+ },
+ },
provider: {
async getProvider() {
try {
@@ -176,6 +186,7 @@ export const useFredyState = create(
page: 1,
result: [],
},
+ features: {},
generalSettings: { settings: {} },
demoMode: { demoMode: false },
versionUpdate: {},
@@ -192,6 +203,7 @@ export const useFredyState = create(
versionUpdate: { ...effects.versionUpdate },
listingsTable: { ...effects.listingsTable },
provider: { ...effects.provider },
+ features: { ...effects.features },
jobs: { ...effects.jobs },
user: { ...effects.user },
};
diff --git a/ui/src/views/listings/management/ListingManagement.jsx b/ui/src/views/listings/management/WatchlistManagement.jsx
similarity index 95%
rename from ui/src/views/listings/management/ListingManagement.jsx
rename to ui/src/views/listings/management/WatchlistManagement.jsx
index 0023e0c..b80820f 100644
--- a/ui/src/views/listings/management/ListingManagement.jsx
+++ b/ui/src/views/listings/management/WatchlistManagement.jsx
@@ -5,7 +5,7 @@ import { Banner, Button, Checkbox, Space } from '@douyinfe/semi-ui';
import NotificationAdapterMutator from '../../jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx';
import Headline from '../../../components/headline/Headline.jsx';
-export default function ListingManagement() {
+export default function WatchlistManagement() {
const [notificationChooserVisible, setNotificationChooserVisible] = useState(false);
const [notificationAdapterData, setNotificationAdapterData] = useState([]);
//TODO: Set default
@@ -29,7 +29,7 @@ export default function ListingManagement() {
setActivityChanges(e.target.checked)}>
- Listing state changes
+ Listing state changes (e.g. listing becomes inactive)
setPriceChanges(e.target.checked)}>
Listing price changes